mirror of
https://github.com/ArthurDanjou/ui.git
synced 2026-01-26 09:50:33 +01:00
docs(app): framework select global (#2719)
Co-authored-by: harlan <harlan@harlanzw.com>
This commit is contained in:
@@ -73,24 +73,41 @@ useServerSeoMeta({
|
||||
twitterCard: 'summary_large_image'
|
||||
})
|
||||
|
||||
const updatedNavigation = computed(() => navigation.value?.map(item => ({
|
||||
...item,
|
||||
children: item.children?.map((child: typeof item) => ({
|
||||
...child,
|
||||
...(child.path === '/getting-started/installation' && {
|
||||
title: 'Installation',
|
||||
active: route.path.startsWith('/getting-started/installation'),
|
||||
children: []
|
||||
}),
|
||||
...(child.path === '/getting-started/i18n' && {
|
||||
title: 'I18n',
|
||||
active: route.path.startsWith('/getting-started/i18n'),
|
||||
children: []
|
||||
})
|
||||
})) || []
|
||||
})))
|
||||
const { framework, frameworks } = useSharedData()
|
||||
|
||||
provide('navigation', updatedNavigation)
|
||||
const groups = computed(() => {
|
||||
return [{
|
||||
id: 'framework',
|
||||
label: 'Framework',
|
||||
items: frameworks.value
|
||||
}]
|
||||
})
|
||||
|
||||
function filterFrameworkItems(items: any[]) {
|
||||
return items?.filter(item => !item.framework || item.framework === framework.value)
|
||||
}
|
||||
|
||||
function processNavigationItem(item: any): any {
|
||||
if (item.shadow) {
|
||||
const matchingChild = filterFrameworkItems(item.children)?.[0]
|
||||
return matchingChild
|
||||
? {
|
||||
...matchingChild,
|
||||
title: item.title,
|
||||
children: matchingChild.children ? processNavigationItem(matchingChild) : undefined
|
||||
}
|
||||
: item
|
||||
}
|
||||
|
||||
return {
|
||||
...item,
|
||||
children: item.children?.length ? filterFrameworkItems(item.children)?.map(processNavigationItem) : undefined
|
||||
}
|
||||
}
|
||||
|
||||
const filteredNavigation = computed(() => navigation.value?.map(processNavigationItem))
|
||||
|
||||
provide('navigation', filteredNavigation)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -111,7 +128,13 @@ provide('navigation', updatedNavigation)
|
||||
<Footer />
|
||||
|
||||
<ClientOnly>
|
||||
<LazyUContentSearch v-model:search-term="searchTerm" :files="files" :navigation="navigation" :fuse="{ resultLimit: 42 }" />
|
||||
<LazyUContentSearch
|
||||
v-model:search-term="searchTerm"
|
||||
:files="files"
|
||||
:groups="groups"
|
||||
:navigation="filteredNavigation"
|
||||
:fuse="{ resultLimit: 42 }"
|
||||
/>
|
||||
</ClientOnly>
|
||||
</template>
|
||||
</UApp>
|
||||
|
||||
10
docs/app/assets/icons/icones-js.svg
Normal file
10
docs/app/assets/icons/icones-js.svg
Normal file
@@ -0,0 +1,10 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 283.46 283.46">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1{fill:#231815;}
|
||||
@media (prefers-color-scheme: dark) { .cls-1{fill:#ffffff;} }
|
||||
</style>
|
||||
</defs>
|
||||
<path class="cls-1" d="M144.89,89.86c-33.46,0-54.44,14.56-66.14,26.76a86,86,0,0,0-23.69,58.94c0,22.64,8.81,43.48,24.8,58.67,15.7,14.92,36.9,23.14,59.68,23.14,23.81,0,46-8.49,62.49-23.91,17-15.9,26.37-37.93,26.37-62C228.4,120.37,185.94,89.86,144.89,89.86Zm.49,153.67a61.49,61.49,0,0,1-46.45-20.4c-12.33-13.76-18.85-32.64-18.85-54.62,0-20.7,6.07-37.67,17.57-49.07,10.11-10,24.39-15.62,40.19-15.74,19,0,35.22,6.56,46.76,19,12.6,13.58,19.27,34,19.27,58.95C203.87,224.39,174.49,243.53,145.38,243.53Z"/>
|
||||
<polygon class="cls-1" points="198.75 74.96 179.45 74.96 142.09 37.83 104.51 74.96 86.14 74.96 138.09 24.25 146.81 24.25 198.75 74.96"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 855 B |
25
docs/app/components/FrameworkSelect.vue
Normal file
25
docs/app/components/FrameworkSelect.vue
Normal file
@@ -0,0 +1,25 @@
|
||||
<script setup lang="ts">
|
||||
const { framework, frameworks } = useSharedData()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UDropdownMenu
|
||||
v-slot="{ open }"
|
||||
:modal="false"
|
||||
:items="frameworks"
|
||||
:ui="{ content: 'w-(--radix-dropdown-menu-trigger-width)' }"
|
||||
>
|
||||
<UButton
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
v-bind="frameworks.find(f => f.value === framework)"
|
||||
block
|
||||
trailing-icon="i-lucide-chevron-down"
|
||||
:class="[open && 'bg-[var(--ui-bg-elevated)]']"
|
||||
:ui="{
|
||||
trailingIcon: ['transition-transform duration-200', open ? 'rotate-180' : undefined].filter(Boolean).join(' ')
|
||||
}"
|
||||
class="-mx-2 w-[calc(100%+1rem)]"
|
||||
/>
|
||||
</UDropdownMenu>
|
||||
</template>
|
||||
@@ -55,6 +55,8 @@ defineShortcuts({
|
||||
|
||||
<USeparator type="dashed" class="my-4" />
|
||||
|
||||
<FrameworkSelect class="mb-4" />
|
||||
|
||||
<UContentNavigation :navigation="navigation" highlight />
|
||||
</template>
|
||||
</UHeader>
|
||||
|
||||
@@ -272,9 +272,9 @@ const { data: ast } = await useAsyncData(`component-code-${name}-${hash({ props:
|
||||
<div v-if="component" class="flex justify-center border border-b-0 border-[var(--ui-border-muted)] relative p-4 z-[1]" :class="[!options.length && 'rounded-t-[calc(var(--ui-radius)*1.5)]', props.class]">
|
||||
<component :is="component" v-bind="{ ...componentProps, ...componentEvents }">
|
||||
<template v-for="slot in Object.keys(slots || {})" :key="slot" #[slot]>
|
||||
<slot :name="slot">
|
||||
<MDCSlot :name="slot" unwrap="p">
|
||||
{{ slots?.[slot] }}
|
||||
</slot>
|
||||
</MDCSlot>
|
||||
</template>
|
||||
</component>
|
||||
</div>
|
||||
|
||||
@@ -4,14 +4,19 @@ import { camelCase } from 'scule'
|
||||
import * as theme from '#build/ui'
|
||||
|
||||
const route = useRoute()
|
||||
const { framework } = useSharedData()
|
||||
|
||||
const name = camelCase(route.params.slug?.[route.params.slug.length - 1] ?? '')
|
||||
|
||||
const strippedCompoundVariants = ref(false)
|
||||
|
||||
function stripCompoundVariants(component?: any) {
|
||||
if (component?.compoundVariants) {
|
||||
component.compoundVariants = component.compoundVariants.filter((compoundVariant: any) => {
|
||||
const strippedTheme = computed(() => {
|
||||
const strippedTheme = {
|
||||
...(theme as any)[name]
|
||||
}
|
||||
|
||||
if (strippedTheme?.compoundVariants) {
|
||||
strippedTheme.compoundVariants = strippedTheme.compoundVariants.filter((compoundVariant: any) => {
|
||||
if (compoundVariant.color) {
|
||||
if (!['primary', 'neutral'].includes(compoundVariant.color)) {
|
||||
strippedCompoundVariants.value = true
|
||||
@@ -40,24 +45,43 @@ function stripCompoundVariants(component?: any) {
|
||||
})
|
||||
}
|
||||
|
||||
return component
|
||||
}
|
||||
return strippedTheme
|
||||
})
|
||||
|
||||
const component = computed(() => {
|
||||
return {
|
||||
ui: {
|
||||
[name]: stripCompoundVariants((theme as any)[name])
|
||||
[name]: strippedTheme.value
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const { data: ast } = await useAsyncData(`component-theme-${name}`, async () => {
|
||||
const { data: ast } = await useAsyncData(`component-theme-${name}-${framework.value}`, async () => {
|
||||
const md = `
|
||||
::code-collapse
|
||||
${framework.value === 'nuxt'
|
||||
? `
|
||||
\`\`\`ts [app.config.ts]
|
||||
export default defineAppConfig(${json5.stringify(component.value, null, 2).replace(/,([ |\t\n]+[}|\])])/g, '$1')})
|
||||
\`\`\`\
|
||||
`
|
||||
: `
|
||||
\`\`\`ts [vite.config.ts]
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import ui from '@nuxt/ui/vite'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
vue(),
|
||||
ui(${json5.stringify(component.value, null, 2).replace(/,([ |\t\n]+[}|\])])/g, '$1')
|
||||
.split('\n')
|
||||
.map((line, i) => i === 0 ? line : ` ${line}`)
|
||||
.join('\n')})
|
||||
]
|
||||
})
|
||||
\`\`\`
|
||||
`}
|
||||
::
|
||||
|
||||
${strippedCompoundVariants.value
|
||||
@@ -69,7 +93,7 @@ Some colors in \`compoundVariants\` are omitted for readability. Check out the s
|
||||
`
|
||||
|
||||
return parseMarkdown(md)
|
||||
})
|
||||
}, { watch: [framework] })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
7
docs/app/components/content/FrameworkOnly.vue
Normal file
7
docs/app/components/content/FrameworkOnly.vue
Normal file
@@ -0,0 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
const { framework } = useSharedData()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<slot :name="framework" />
|
||||
</template>
|
||||
@@ -2,8 +2,13 @@
|
||||
import json5 from 'json5'
|
||||
import icons from '../../../../src/theme/icons'
|
||||
|
||||
const { data: ast } = await useAsyncData(`icons-theme`, async () => {
|
||||
const { framework } = useSharedData()
|
||||
|
||||
const { data: ast } = await useAsyncData(`icons-theme-${framework.value}`, async () => {
|
||||
const md = `
|
||||
::code-collapse
|
||||
${framework.value === 'nuxt'
|
||||
? `
|
||||
\`\`\`ts [app.config.ts]
|
||||
export default defineAppConfig(${json5.stringify({
|
||||
ui: {
|
||||
@@ -11,10 +16,33 @@ export default defineAppConfig(${json5.stringify({
|
||||
}
|
||||
}, null, 2).replace(/,([ |\t\n]+[}|\])])/g, '$1')})
|
||||
\`\`\`\
|
||||
`
|
||||
: `
|
||||
\`\`\`ts [vite.config.ts]
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import ui from '@nuxt/ui/vite'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
vue(),
|
||||
ui(${json5.stringify({
|
||||
ui: {
|
||||
icons
|
||||
}
|
||||
}, null, 2).replace(/,([ |\t\n]+[}|\])])/g, '$1')
|
||||
.split('\n')
|
||||
.map((line, i) => i === 0 ? line : ` ${line}`)
|
||||
.join('\n')})
|
||||
]
|
||||
})
|
||||
\`\`\`
|
||||
`}
|
||||
::
|
||||
`
|
||||
|
||||
return parseMarkdown(md)
|
||||
})
|
||||
}, { watch: [framework] })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -56,7 +56,7 @@ defineProps({
|
||||
<h1 class="m-0 text-[75px] font-semibold mb-2 text-white flex items-center">
|
||||
<span>{{ title }}</span>
|
||||
</h1>
|
||||
<p v-if="description" class="text-[32px] text-[#94a3b8] leading-tight">
|
||||
<p v-if="description" class="text-[32px] text-[#94a3b8] leading-tight text-balance">
|
||||
{{ description.slice(0, 200) }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
23
docs/app/composables/useSharedData.ts
Normal file
23
docs/app/composables/useSharedData.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { createSharedComposable } from '@vueuse/core'
|
||||
|
||||
function _useSharedData() {
|
||||
const framework = useCookie('nuxt-ui-framework', { default: () => 'nuxt' })
|
||||
const frameworks = computed(() => [{
|
||||
label: 'Nuxt',
|
||||
icon: 'i-logos-nuxt-icon',
|
||||
value: 'nuxt',
|
||||
onSelect: () => framework.value = 'nuxt'
|
||||
}, {
|
||||
label: 'Vue',
|
||||
icon: 'i-logos-vue',
|
||||
value: 'vue',
|
||||
onSelect: () => framework.value = 'vue'
|
||||
}].map(f => ({ ...f, active: framework.value === f.value })))
|
||||
|
||||
return {
|
||||
framework,
|
||||
frameworks
|
||||
}
|
||||
}
|
||||
|
||||
export const useSharedData = createSharedComposable(_useSharedData)
|
||||
@@ -10,6 +10,10 @@ const navigation = inject<Ref<ContentNavigationItem[]>>('navigation')
|
||||
<UPage>
|
||||
<template #left>
|
||||
<UPageAside>
|
||||
<template #top>
|
||||
<FrameworkSelect />
|
||||
</template>
|
||||
|
||||
<UContentNavigation :navigation="navigation" highlight />
|
||||
</UPageAside>
|
||||
</template>
|
||||
|
||||
@@ -21,6 +21,28 @@ const navigation = inject<Ref<ContentNavigationItem[]>>('navigation')
|
||||
|
||||
const breadcrumb = computed(() => mapContentNavigation(findPageBreadcrumb(navigation?.value, page.value)).map(({ icon, ...link }) => link))
|
||||
|
||||
const { framework } = useSharedData()
|
||||
|
||||
// Redirect to the correct framework version if the page is not the current framework
|
||||
if (!import.meta.prerender) {
|
||||
watch(framework, () => {
|
||||
if (page.value?.navigation?.framework && page.value?.navigation?.framework !== framework.value) {
|
||||
if (route.path.endsWith(`/${page.value?.navigation?.framework}`)) {
|
||||
navigateTo(`${route.path.split('/').slice(0, -1).join('/')}/${framework.value}`)
|
||||
} else {
|
||||
navigateTo(`/getting-started`)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Update the framework if the page has a different framework
|
||||
watch(page, () => {
|
||||
if (page.value?.navigation?.framework && page.value?.navigation?.framework !== framework.value) {
|
||||
framework.value = page.value?.navigation?.framework as string
|
||||
}
|
||||
}, { immediate: true })
|
||||
|
||||
useSeoMeta({
|
||||
titleTemplate: '%s - Nuxt UI v3',
|
||||
title: typeof page.value.navigation === 'object' && page.value.navigation.title ? page.value.navigation.title : page.value.title,
|
||||
@@ -75,21 +97,6 @@ const communityLinks = computed(() => [{
|
||||
</template>
|
||||
|
||||
<template #links>
|
||||
<UDropdownMenu v-if="page.select" v-slot="{ open }" :items="page.select.items" :content="{ align: 'end' }">
|
||||
<UButton
|
||||
color="neutral"
|
||||
variant="subtle"
|
||||
v-bind="page.select.items.find((item: any) => item.to === route.path)"
|
||||
block
|
||||
trailing-icon="i-lucide-chevron-down"
|
||||
:class="[open && 'bg-[var(--ui-bg-accented)]/75']"
|
||||
:ui="{
|
||||
trailingIcon: ['transition-transform duration-200', open ? 'rotate-180' : undefined].filter(Boolean).join(' ')
|
||||
}"
|
||||
class="w-[128px]"
|
||||
/>
|
||||
</UDropdownMenu>
|
||||
|
||||
<UButton
|
||||
v-for="link in page.links"
|
||||
:key="link.label"
|
||||
|
||||
Reference in New Issue
Block a user