docs: add version select (#532)

This commit is contained in:
Benjamin Canac
2023-08-14 22:11:04 +02:00
parent ee663157b7
commit 7e7e9d0f85
25 changed files with 198 additions and 45 deletions

View File

@@ -8,6 +8,8 @@
<UPage> <UPage>
<template #left> <template #left>
<UAside :links="anchors"> <UAside :links="anchors">
<BranchSelect />
<UNavigationTree :links="mapContentNavigation(navigation)" /> <UNavigationTree :links="mapContentNavigation(navigation)" />
</UAside> </UAside>
</template> </template>
@@ -35,13 +37,27 @@
<script setup lang="ts"> <script setup lang="ts">
const colorMode = useColorMode() const colorMode = useColorMode()
const { prefix, removePrefixFromNavigation, removePrefixFromFiles } = useContentSource()
const { mapContentNavigation } = useElementsHelpers() const { mapContentNavigation } = useElementsHelpers()
const { data: navigation } = await useLazyAsyncData('navigation', () => fetchContentNavigation(), { const { data: navigation } = await useLazyAsyncData('navigation', () => fetchContentNavigation(), {
default: () => [] default: () => [],
transform: (navigation) => {
navigation = navigation.find(link => link._path === prefix.value)?.children || []
return prefix.value === '/main' ? removePrefixFromNavigation(navigation) : navigation
},
watch: [prefix]
}) })
const { data: files } = await useLazyAsyncData('files', () => queryContent().where({ _type: 'markdown', navigation: { $ne: false } }).find(), { const { data: files } = await useLazyAsyncData('files', () => queryContent().where({ _type: 'markdown', navigation: { $ne: false } }).find(), {
default: () => [] default: () => [],
transform: (files) => {
files = files.filter(file => file._path.startsWith(prefix.value))
return prefix.value === '/main' ? removePrefixFromFiles(files) : files
},
watch: [prefix]
}) })
const anchors = [{ const anchors = [{
@@ -89,4 +105,5 @@ useSeoMeta({
// Provide // Provide
provide('navigation', navigation) provide('navigation', navigation)
provide('files', files)
</script> </script>

View File

@@ -0,0 +1,50 @@
<template>
<div class="mb-3 lg:mb-6">
<label for="branch" class="block mb-1.5 font-semibold text-sm/6">Version</label>
<USelectMenu
id="branch"
:model-value="branch"
name="branch"
:options="branches"
color="gray"
:ui="{ icon: { trailing: { padding: { sm: 'pe-1.5' } } } }"
:ui-menu="{ option: { container: 'gap-1.5' } }"
@update:model-value="selectBranch"
>
<template #label>
<UIcon v-if="branch.icon" :name="branch.icon" class="w-4 h-4 flex-shrink-0 text-gray-600 dark:text-gray-300" />
<span class="font-medium">{{ branch.label }}</span>
<span class="truncate text-gray-400 dark:text-gray-500">{{ branch.suffix }}</span>
</template>
<template #option="{ option }">
<UIcon v-if="option.icon" :name="option.icon" class="w-4 h-4 flex-shrink-0 text-gray-600 dark:text-gray-300" />
<span class="font-medium">{{ option.label }}</span>
<span class="truncate text-gray-400 dark:text-gray-500">{{ option.suffix }}</span>
</template>
</USelectMenu>
</div>
</template>
<script setup lang="ts">
const route = useRoute()
const router = useRouter()
const { branches, branch } = useContentSource()
function selectBranch (branch) {
if (branch.name === 'dev') {
if (route.path.startsWith('/dev')) {
return
}
router.push(`/dev${route.path}`)
} else {
router.push(route.path.replace('/dev', ''))
}
}
</script>

View File

@@ -20,6 +20,8 @@
</template> </template>
<template #panel> <template #panel>
<BranchSelect />
<UNavigationTree :links="mapContentNavigation(navigation)" /> <UNavigationTree :links="mapContentNavigation(navigation)" />
</template> </template>
</UHeader> </UHeader>

View File

@@ -115,7 +115,7 @@ const componentProps = reactive({ ...props.props })
const appConfig = useAppConfig() const appConfig = useAppConfig()
const route = useRoute() const route = useRoute()
// eslint-disable-next-line vue/no-dupe-keys // eslint-disable-next-line vue/no-dupe-keys
const slug = props.slug || route.params.slug[1] const slug = props.slug || route.params.slug[route.params.slug.length - 1]
const camelName = useCamelCase(slug) const camelName = useCamelCase(slug)
const name = `U${useUpperFirst(camelName)}` const name = `U${useUpperFirst(camelName)}`

View File

@@ -16,7 +16,7 @@ const props = defineProps({
const appConfig = useAppConfig() const appConfig = useAppConfig()
const route = useRoute() const route = useRoute()
// eslint-disable-next-line vue/no-dupe-keys // eslint-disable-next-line vue/no-dupe-keys
const slug = props.slug || route.params.slug[1] const slug = props.slug || route.params.slug[route.params.slug.length - 1]
const camelName = useCamelCase(slug) const camelName = useCamelCase(slug)
const name = `U${useUpperFirst(camelName)}` const name = `U${useUpperFirst(camelName)}`

View File

@@ -29,7 +29,7 @@ const props = defineProps({
const route = useRoute() const route = useRoute()
// eslint-disable-next-line vue/no-dupe-keys // eslint-disable-next-line vue/no-dupe-keys
const slug = props.slug || route.params.slug[1] const slug = props.slug || route.params.slug[route.params.slug.length - 1]
const camelName = useCamelCase(slug) const camelName = useCamelCase(slug)
const name = `U${useUpperFirst(camelName)}` const name = `U${useUpperFirst(camelName)}`

View File

@@ -27,7 +27,7 @@ const props = defineProps({
const route = useRoute() const route = useRoute()
// eslint-disable-next-line vue/no-dupe-keys // eslint-disable-next-line vue/no-dupe-keys
const slug = props.slug || route.params.slug[1] const slug = props.slug || route.params.slug[route.params.slug.length - 1]
const camelName = useCamelCase(slug) const camelName = useCamelCase(slug)
const name = `U${useUpperFirst(camelName)}` const name = `U${useUpperFirst(camelName)}`

View File

@@ -2,8 +2,7 @@
const commandPaletteRef = ref() const commandPaletteRef = ref()
const navigation = inject('navigation') const navigation = inject('navigation')
const files = inject('files')
const { data: files } = await useLazyAsyncData('search', () => queryContent().where({ _type: 'markdown' }).find(), { default: () => [] })
const groups = computed(() => navigation.value.map(item => ({ const groups = computed(() => navigation.value.map(item => ({
key: item._path, key: item._path,

View File

@@ -0,0 +1,57 @@
import type { NavItem, ParsedContent } from '@nuxt/content/dist/runtime/types'
export const useContentSource = () => {
const route = useRoute()
const config = useRuntimeConfig().public
const branches = [{
name: 'dev',
icon: 'i-heroicons-cube',
suffix: 'dev',
label: 'Edge'
}, {
name: 'main',
icon: 'i-heroicons-cube',
suffix: 'latest',
label: `v${config.version}`
}]
const branch = computed(() => branches.find(b => b.name === (route.path.startsWith('/dev') ? 'dev' : 'main')))
const prefix = computed(() => `/${branch.value.name}`)
function removePrefixFromNavigation (navigation: NavItem[]): NavItem[] {
return navigation.map((link) => {
const { _path, children, ...rest } = link
return {
...rest,
_path: route.path.startsWith(prefix.value) ? _path : _path.replace(new RegExp(`^${prefix.value}`, 'g'), ''),
children: children?.length ? removePrefixFromNavigation(children) : undefined
}
})
}
function removePrefixFromFiles (files: ParsedContent[]) {
return files.map((file) => {
if (!file) {
return
}
const { _path, ...rest } = file
return {
...rest,
_path: route.path.startsWith(prefix.value) ? _path : _path.replace(new RegExp(`^${prefix.value}`, 'g'), '')
}
})
}
return {
branches,
branch,
prefix,
removePrefixFromNavigation,
removePrefixFromFiles
}
}

View File

@@ -37,7 +37,7 @@ Likewise, you can't define a `primary` color in your `tailwind.config.ts` as it
We'd advise you to use those colors in your components and pages, e.g. `text-primary-500 dark:text-primary-400`, `bg-gray-100 dark:bg-gray-900`, etc. so your app automatically adapts when changing your `app.config.ts`. We'd advise you to use those colors in your components and pages, e.g. `text-primary-500 dark:text-primary-400`, `bg-gray-100 dark:bg-gray-900`, etc. so your app automatically adapts when changing your `app.config.ts`.
:: ::
The `primary` color also has a `DEFAULT` shade that changes based on the theme. It is `500` in light mode and `400` in dark mode. You can use as a shortcut in your components and pages, e.g. `text-primary`, `bg-primary`, `focus-visible:ring-primary`, etc. :u-badge{label="Edge" class="!rounded-full" variant="subtle"} The `primary` color also has a `DEFAULT` shade that changes based on the theme. It is `500` in light mode and `400` in dark mode. You can use as a shortcut in your components and pages, e.g. `text-primary`, `bg-primary`, `focus-visible:ring-primary`, etc. :u-badge{label="New" class="!rounded-full" variant="subtle"}
### Smart Safelisting ### Smart Safelisting
@@ -107,7 +107,7 @@ Each component has a `ui` prop that allows you to customize everything specifica
You can find the default classes for each component under the `Preset` section. You can find the default classes for each component under the `Preset` section.
:: ::
Thanks to [tailwind-merge](https://github.com/dcastil/tailwind-merge), the `ui` prop is smartly merged with the config. This means you don't have to rewrite everything. :u-badge{label="Edge" class="!rounded-full" variant="subtle"} Thanks to [tailwind-merge](https://github.com/dcastil/tailwind-merge), the `ui` prop is smartly merged with the config. This means you don't have to rewrite everything. :u-badge{label="New" class="!rounded-full" variant="subtle"}
For example, the default preset of the `FormGroup` component looks like this: For example, the default preset of the `FormGroup` component looks like this:
@@ -142,7 +142,7 @@ You can also use the `class` attribute to add classes to the component.
</template> </template>
``` ```
Again, with [tailwind-merge](https://github.com/dcastil/tailwind-merge), this will smartly merge the classes with the `ui` prop and the config. :u-badge{label="Edge" class="!rounded-full" variant="subtle"} Again, with [tailwind-merge](https://github.com/dcastil/tailwind-merge), this will smartly merge the classes with the `ui` prop and the config. :u-badge{label="New" class="!rounded-full" variant="subtle"}
### Default values ### Default values

View File

@@ -4,8 +4,6 @@ links:
- label: GitHub - label: GitHub
icon: i-simple-icons-github icon: i-simple-icons-github
to: https://github.com/nuxtlabs/ui/blob/dev/src/runtime/components/elements/Alert.vue to: https://github.com/nuxtlabs/ui/blob/dev/src/runtime/components/elements/Alert.vue
navigation:
badge: New
--- ---
## Usage ## Usage

View File

@@ -53,7 +53,7 @@ baseProps:
If there is an error loading the `src` of the avatar or `src` is null / false a background placeholder will be displayed, customizable in `ui.avatar.background`. If there is an error loading the `src` of the avatar or `src` is null / false a background placeholder will be displayed, customizable in `ui.avatar.background`.
#### Icon :u-badge{label="Edge" class="ml-2 align-text-bottom !rounded-full" variant="subtle"} #### Icon :u-badge{label="New" class="ml-2 align-text-bottom !rounded-full" variant="subtle"}
Use any icon from [Iconify](https://icones.js.org) by setting the `icon` prop by using this pattern: `i-{collection_name}-{icon_name}` or change it globally in `ui.avatar.default.icon` to display an icon on top of the background. Use any icon from [Iconify](https://icones.js.org) by setting the `icon` prop by using this pattern: `i-{collection_name}-{icon_name}` or change it globally in `ui.avatar.default.icon` to display an icon on top of the background.

View File

@@ -31,7 +31,7 @@ props:
Use the `color` and `variant` props to change the visual style of the Badge. Use the `color` and `variant` props to change the visual style of the Badge.
- `variant` can be `solid` (default), `outline`, `soft` or `subtle`. :u-badge{label="New" class="!rounded-full" variant="subtle"} - `variant` can be `solid` (default), `outline`, `soft` or `subtle`.
::component-card ::component-card
--- ---
@@ -45,7 +45,7 @@ Badge
Besides all the colors from the `ui.colors` object, you can also use the `white` and `black` colors with their pre-defined variants. Besides all the colors from the `ui.colors` object, you can also use the `white` and `black` colors with their pre-defined variants.
#### White :u-badge{label="New" class="ml-2 align-text-bottom !rounded-full" variant="subtle"} #### White
::component-card ::component-card
--- ---
@@ -62,7 +62,7 @@ excludedProps:
Badge Badge
:: ::
#### Gray :u-badge{label="New" class="ml-2 align-text-bottom !rounded-full" variant="subtle"} #### Gray
::component-card ::component-card
--- ---
@@ -79,7 +79,7 @@ excludedProps:
Badge Badge
:: ::
#### Black :u-badge{label="New" class="ml-2 align-text-bottom !rounded-full" variant="subtle"} #### Black
::component-card ::component-card
--- ---

View File

@@ -5,8 +5,6 @@ links:
- label: GitHub - label: GitHub
icon: i-simple-icons-github icon: i-simple-icons-github
to: https://github.com/nuxtlabs/ui/blob/dev/src/runtime/components/elements/Link.vue to: https://github.com/nuxtlabs/ui/blob/dev/src/runtime/components/elements/Link.vue
navigation:
badge: New
--- ---
## Usage ## Usage

View File

@@ -4,8 +4,6 @@ links:
- label: GitHub - label: GitHub
icon: i-simple-icons-github icon: i-simple-icons-github
to: https://github.com/nuxtlabs/ui/blob/dev/src/runtime/components/forms/Form.ts to: https://github.com/nuxtlabs/ui/blob/dev/src/runtime/components/forms/Form.ts
navigation:
badge: New
--- ---
## Usage ## Usage

View File

@@ -108,7 +108,7 @@ const selected = ref(people[0])
``` ```
:: ::
If you only want to select a single object property rather than the whole object as value, you can set the `value-attribute` property. This prop defaults to `null`. :u-badge{label="New" class="!rounded-full" variant="subtle"} If you only want to select a single object property rather than the whole object as value, you can set the `value-attribute` property. This prop defaults to `null`.
::component-example ::component-example
#default #default

View File

@@ -159,7 +159,7 @@ code: >-
Learn more about form validation in the `Form` component. Learn more about form validation in the `Form` component.
:: ::
### Size :u-badge{label="New" class="ml-2 align-text-bottom !rounded-full" variant="subtle"} ### Size
Use the `size` prop to change the size of the label and the form element. Use the `size` prop to change the size of the label and the form element.

View File

@@ -4,8 +4,6 @@ links:
- label: GitHub - label: GitHub
icon: i-simple-icons-github icon: i-simple-icons-github
to: https://github.com/nuxtlabs/ui/blob/dev/src/runtime/components/navigation/Tabs.vue to: https://github.com/nuxtlabs/ui/blob/dev/src/runtime/components/navigation/Tabs.vue
navigation:
badge: New
--- ---
## Usage ## Usage
@@ -87,7 +85,7 @@ const items = [...]
This will have no effect if you are using a `v-model` to control the selected index. This will have no effect if you are using a `v-model` to control the selected index.
:: ::
### Listen to changes :u-badge{label="Edge" class="ml-2 align-text-bottom !rounded-full" variant="subtle"} ### Listen to changes :u-badge{label="New" class="ml-2 align-text-bottom !rounded-full" variant="subtle"}
You can listen to changes by using the `@change` event. The event will emit the index of the selected item. You can listen to changes by using the `@change` event. The event will emit the index of the selected item.
@@ -113,7 +111,7 @@ function onChange (index) {
``` ```
:: ::
### Control the selected index :u-badge{label="Edge" class="ml-2 align-text-bottom !rounded-full" variant="subtle"} ### Control the selected index :u-badge{label="New" class="ml-2 align-text-bottom !rounded-full" variant="subtle"}
Use a `v-model` to control the selected index. Use a `v-model` to control the selected index.

View File

@@ -316,7 +316,7 @@ excludedProps:
## Slots ## Slots
### `title` / `description` :u-badge{label="New" class="ml-2 align-text-bottom !rounded-full" variant="subtle"} ### `title` / `description`
Use the `#title` and `#description` slots to customize the Notification. Use the `#title` and `#description` slots to customize the Notification.

View File

@@ -21,6 +21,8 @@
<script setup lang="ts"> <script setup lang="ts">
import type { NuxtError } from '#app' import type { NuxtError } from '#app'
const { prefix, removePrefixFromNavigation, removePrefixFromFiles } = useContentSource()
useSeoMeta({ useSeoMeta({
title: 'Page not found', title: 'Page not found',
description: 'We are sorry but this page could not be found.' description: 'We are sorry but this page could not be found.'
@@ -30,14 +32,26 @@ defineProps<{
error: NuxtError error: NuxtError
}>() }>()
const { data: navigation } = await useAsyncData('navigation', () => fetchContentNavigation(), { const { data: navigation } = await useLazyAsyncData('navigation', () => fetchContentNavigation(), {
default: () => [] default: () => [],
transform: (navigation) => {
navigation = navigation.find(link => link._path === prefix.value)?.children || []
return prefix.value === '/main' ? removePrefixFromNavigation(navigation) : navigation
}
}) })
const { data: files } = await useLazyAsyncData('files', () => queryContent().where({ _type: 'markdown', navigation: { $ne: false } }).find(), { const { data: files } = await useLazyAsyncData('files', () => queryContent().where({ _type: 'markdown', navigation: { $ne: false } }).find(), {
default: () => [] default: () => [],
transform: (files) => {
files = files.filter(file => file._path.startsWith(prefix.value))
return prefix.value === '/main' ? removePrefixFromFiles(files) : files
}
}) })
// Provide // Provide
provide('navigation', navigation) provide('navigation', navigation)
provide('files', files)
</script> </script>

View File

@@ -7,7 +7,7 @@ import pkg from '../package.json'
const { resolve } = createResolver(import.meta.url) const { resolve } = createResolver(import.meta.url)
export default defineNuxtConfig({ export default defineNuxtConfig({
extends: '@nuxthq/elements', extends: process.env.NUXT_ELEMENTS_PATH || '@nuxthq/elements',
modules: [ modules: [
'@nuxt/content', '@nuxt/content',
// '@nuxt/devtools', // '@nuxt/devtools',
@@ -30,6 +30,23 @@ export default defineNuxtConfig({
icons: ['heroicons', 'simple-icons'], icons: ['heroicons', 'simple-icons'],
safelistColors: excludeColors(colors) safelistColors: excludeColors(colors)
}, },
content: {
sources: {
// overwrite default source AKA `content` directory
content: {
prefix: '/dev',
driver: 'fs',
base: resolve('./content')
},
main: {
prefix: '/main',
driver: 'github',
repo: 'nuxtlabs/ui',
branch: 'main',
dir: 'docs/content'
}
}
},
googleFonts: { googleFonts: {
families: { families: {
Inter: [400, 500, 600, 700] Inter: [400, 500, 600, 700]
@@ -40,7 +57,7 @@ export default defineNuxtConfig({
}, },
nitro: { nitro: {
prerender: { prerender: {
routes: ['/getting-started'] routes: ['/getting-started', '/dev/getting-started']
} }
}, },
experimental: { experimental: {

View File

@@ -10,7 +10,7 @@
"@nuxt/content": "^2.7.2", "@nuxt/content": "^2.7.2",
"@nuxt/devtools": "^0.8.0", "@nuxt/devtools": "^0.8.0",
"@nuxt/eslint-config": "^0.1.1", "@nuxt/eslint-config": "^0.1.1",
"@nuxthq/elements": "npm:@nuxthq/elements-edge@0.0.1-28197629.5e2d155", "@nuxthq/elements": "npm:@nuxthq/elements-edge@0.0.1-28198976.e8e2f70",
"@nuxthq/studio": "^0.13.4", "@nuxthq/studio": "^0.13.4",
"@nuxtjs/fontaine": "^0.4.1", "@nuxtjs/fontaine": "^0.4.1",
"@nuxtjs/google-fonts": "^3.0.2", "@nuxtjs/google-fonts": "^3.0.2",

View File

@@ -11,7 +11,7 @@
<hr v-if="surround?.length" class="my-8"> <hr v-if="surround?.length" class="my-8">
<UDocsSurround :surround="surround" /> <UDocsSurround :surround="removePrefixFromFiles(surround)" />
<Footer class="not-prose" /> <Footer class="not-prose" />
</UPageBody> </UPageBody>
@@ -24,17 +24,21 @@
<script setup lang="ts"> <script setup lang="ts">
const route = useRoute() const route = useRoute()
const { prefix, removePrefixFromFiles } = useContentSource()
const { findPageHeadline } = useElementsHelpers() const { findPageHeadline } = useElementsHelpers()
const { data: page } = await useAsyncData(`docs-${route.path}`, () => queryContent(route.path).findOne()) const path = computed(() => route.path.startsWith(prefix.value) ? route.path : `${prefix.value}${route.path}`)
const { data: page } = await useAsyncData(path.value, () => queryContent(path.value).findOne())
if (!page.value) { if (!page.value) {
throw createError({ statusCode: 404, statusMessage: 'Page not found' }) throw createError({ statusCode: 404, statusMessage: 'Page not found' })
} }
const { data: surround } = await useAsyncData(`docs-${route.path}-surround`, () => queryContent() const { data: surround } = await useAsyncData(`${path.value}-surround`, () => {
.where({ _extension: 'md', navigation: { $ne: false } }) return queryContent(prefix.value)
.findSurround(route.path.endsWith('/') ? route.path.slice(0, -1) : route.path) .where({ _extension: 'md', navigation: { $ne: false } })
) .findSurround((path.value.endsWith('/') ? path.value.slice(0, -1) : path.value))
})
useContentHead(page) useContentHead(page)

1
docs/public/robots.txt Normal file
View File

@@ -0,0 +1 @@
Disallow: /dev/*

8
pnpm-lock.yaml generated
View File

@@ -121,8 +121,8 @@ importers:
specifier: ^0.1.1 specifier: ^0.1.1
version: 0.1.1(eslint@8.47.0) version: 0.1.1(eslint@8.47.0)
'@nuxthq/elements': '@nuxthq/elements':
specifier: npm:@nuxthq/elements-edge@0.0.1-28197629.5e2d155 specifier: npm:@nuxthq/elements-edge@0.0.1-28198976.e8e2f70
version: /@nuxthq/elements-edge@0.0.1-28197629.5e2d155(@nuxt/content@2.7.2)(@nuxthq/ui@)(vue@3.3.4) version: /@nuxthq/elements-edge@0.0.1-28198976.e8e2f70(@nuxt/content@2.7.2)(@nuxthq/ui@)(vue@3.3.4)
'@nuxthq/studio': '@nuxthq/studio':
specifier: ^0.13.4 specifier: ^0.13.4
version: 0.13.4(rollup@3.26.2) version: 0.13.4(rollup@3.26.2)
@@ -1852,8 +1852,8 @@ packages:
- vue-tsc - vue-tsc
dev: true dev: true
/@nuxthq/elements-edge@0.0.1-28197629.5e2d155(@nuxt/content@2.7.2)(@nuxthq/ui@)(vue@3.3.4): /@nuxthq/elements-edge@0.0.1-28198976.e8e2f70(@nuxt/content@2.7.2)(@nuxthq/ui@)(vue@3.3.4):
resolution: {integrity: sha512-Kge09WmdOopvvmbuEHoC04zvUa2CnRANH3gg+rnKEaM3rhaBe7s8b8jGzJTWyKIIs1Fz53lrd80feigF7tG84A==} resolution: {integrity: sha512-i3d+HD6JaA7hSgQGkaAS36rSqHWUwa7550KqRIpyOs3VsJ/gT9ry8NW1dRpNamUnGAJqhAt6Btr4NyXDKPfJdQ==}
peerDependencies: peerDependencies:
'@nuxt/content': ^2.7.2 '@nuxt/content': ^2.7.2
'@nuxthq/ui': npm:@nuxthq/ui-edge@latest '@nuxthq/ui': npm:@nuxthq/ui-edge@latest