docs(showcase): update

This commit is contained in:
Benjamin Canac
2025-04-03 11:59:37 +02:00
parent 52a97e2df7
commit 7ac17ae7e8
11 changed files with 98 additions and 70 deletions

View File

@@ -165,12 +165,12 @@ useIntersectionObserver(contributorsRef, ([entry]) => {
<circle cx="6.53711" cy="37.4551" r="1.5" fill="var(--ui-border-accented)" /> <circle cx="6.53711" cy="37.4551" r="1.5" fill="var(--ui-border-accented)" />
<circle cx="38.5957" cy="37.4551" r="1.5" fill="var(--ui-border-accented)" /> <circle cx="38.5957" cy="37.4551" r="1.5" fill="var(--ui-border-accented)" />
</svg> </svg>
<UIcon :name="feature.icon" class="size-5 flex-shrink-0" /> <UIcon :name="feature.icon" class="size-5 shrink-0" />
</div> </div>
<div class="flex flex-col"> <div class="flex flex-col">
<h2 class="font-medium text-(--ui-text-highlighted) inline-flex items-center gap-x-1"> <h2 class="font-medium text-(--ui-text-highlighted) inline-flex items-center gap-x-1">
{{ feature.title }} {{ feature.title }}
<UIcon v-if="feature.to" name="i-lucide-arrow-right" class="size-4 flex-shrink-0 opacity-0 group-hover:opacity-100 transition-all duration-200 -translate-x-1 group-hover:translate-x-0" /> <UIcon v-if="feature.to" name="i-lucide-arrow-right" class="size-4 shrink-0 opacity-0 group-hover:opacity-100 transition-all duration-200 -translate-x-1 group-hover:translate-x-0" />
</h2> </h2>
<p class="text-sm text-(--ui-text-muted)"> <p class="text-sm text-(--ui-text-muted)">
{{ feature.description }} {{ feature.description }}

View File

@@ -37,12 +37,12 @@ useSeoMeta({
<div aria-hidden="true" class="hidden lg:block absolute z-[-1] border-x border-(--ui-border) inset-0 mx-4 sm:mx-6 lg:mx-8" /> <div aria-hidden="true" class="hidden lg:block absolute z-[-1] border-x border-(--ui-border) inset-0 mx-4 sm:mx-6 lg:mx-8" />
<div class="border border-(--ui-border) bg-(--ui-bg)"> <div class="border-l border-t border-(--ui-border)">
<ul class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 items-start justify-center"> <ul class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 items-start justify-center divide-y divide-x divide-(--ui-border)">
<li <li
v-for="item in page.items" v-for="item in page.items"
:key="item.name" :key="item.name"
class="relative flex flex-col gap-y-4 justify-start group h-full p-4 hover:bg-(--ui-bg-elevated)/50 border-(--ui-border) border-b max-sm:last:border-b-0 border-r max-sm:border-r-0 sm:even:border-r-0 lg:even:border-r lg:border-r lg:[&:nth-child(4n)]:border-r-0 lg:[&:nth-child(5n)]:border-b-0 lg:[&:nth-child(6n)]:border-b-0" class="group relative flex items-center justify-center flex-1 size-full p-2 last:border-r last:border-b border-(--ui-border) overflow-hidden"
> >
<NuxtLink class="inset-0 absolute" :to="item.url" target="_blank"> <NuxtLink class="inset-0 absolute" :to="item.url" target="_blank">
<span class="sr-only">Go to {{ item.name }}</span> <span class="sr-only">Go to {{ item.name }}</span>
@@ -51,20 +51,34 @@ useSeoMeta({
<NuxtImg <NuxtImg
:src="`/assets/showcase/${item.name.toLowerCase().replace(/\s/g, '-')}.png`" :src="`/assets/showcase/${item.name.toLowerCase().replace(/\s/g, '-')}.png`"
:alt="`Screenshot of ${item.name}`" :alt="`Screenshot of ${item.name}`"
width="311" width="327"
height="194" height="184"
class="rounded-[calc(var(--ui-radius)*1.5)] group-hover:scale-103 duration-200 transition-transform pointer-events-none" :modifiers="{
position: 'top'
}"
class="aspect-[16/9] size-full opacity-75 group-hover:opacity-100 group-hover:scale-110 duration-200 transition-[scale,opacity] pointer-events-none"
/> />
<div class="flex items-center gap-1 px-1"> <div class="absolute flex items-center px-2.5 py-0.75 gap-1 opacity-0 group-hover:opacity-100 transition-opacity duration-200 pointer-events-none bg-black/90 rounded-full">
<span class="font-medium text-(--ui-text-highlighted)"> <span class="text-sm text-(--ui-text-highlighted) font-medium">
{{ item.name }} {{ item.name }}
</span> </span>
<UIcon name="i-lucide-arrow-right" class="size-4 flex-shrink-0 opacity-0 group-hover:opacity-100 transition-all duration-200 -translate-x-1 group-hover:translate-x-0 text-(--ui-text-muted)" /> <UIcon name="i-lucide-arrow-up-right" class="size-4 shrink-0" />
</div> </div>
</li> </li>
</ul> </ul>
</div> </div>
<div class="flex justify-center -mb-[36px]">
<UButton
label="Submit your project"
trailing-icon="i-lucide-plus"
color="neutral"
size="lg"
to="https://github.com/nuxt/ui/edit/v3/docs/content/showcase.yml"
target="_blank"
/>
</div>
</UPageHero> </UPageHero>
</UMain> </UMain>
</template> </template>

View File

@@ -5,15 +5,27 @@ hero:
title: Showcase title: Showcase
description: Discover our selection of projects built with Nuxt UI. description: Discover our selection of projects built with Nuxt UI.
items: items:
- name: Ovatu
url: https://ovatu.com/
- name: Shelve - name: Shelve
url: https://shelve.cloud url: https://shelve.cloud/
- name: Uneed - name: Uneed
url: https://uneed.best url: https://uneed.best/
- name: Details - name: Details
url: https://details.team url: https://details.team/
- name: Espace Asso by Benevolt - name: Espace Asso by Benevolt
url: https://asso.benevolt.fr url: https://asso.benevolt.fr/
- name: Directus Docs - name: Directus Docs
url: https://docs.directus.io url: https://docs.directus.io/
- name: Super SaaS - name: Super SaaS
url: https://supersaas.dev/ url: https://supersaas.dev/
- name: Passionate People
url: https://passionatepeople.io/
- name: The Companies API
url: https://www.thecompaniesapi.com/
- name: Thuprai
url: https://thuprai.com/
- name: Juno.one
url: https://www.juno.one/
- name: Pallyy
url: https://pallyy.com/

View File

@@ -1,54 +0,0 @@
import { defineNuxtModule } from '@nuxt/kit'
import { existsSync } from 'node:fs'
import { join } from 'pathe'
import captureWebsite from 'capture-website'
interface ContentFile {
id?: string
items?: any
}
interface TemplateItem {
name: string
url?: string
screenshotUrl?: string
screenshotOptions?: Record<string, any>
}
export default defineNuxtModule((options, nuxt) => {
nuxt.hook('content:file:afterParse', async ({ content: file }: { content: ContentFile }) => {
if (file.id?.includes('showcase') && file.items) {
const itemsArray: TemplateItem[] = Array.isArray(file.items)
? file.items
: Object.values(file.items)
if (itemsArray.length === 0) {
console.log('No items to process')
return
}
console.log(`Processing ${itemsArray.length} template items`)
for (const template of itemsArray) {
const url = template.screenshotUrl || template.url
if (!url) {
console.error(`Template ${template.name} has no "url" or "screenshotUrl" to take a screenshot from`)
continue
}
const name = template.name.toLowerCase().replace(/\s/g, '-')
const filename = join(process.cwd(), 'docs/public/assets/showcase', `${name}.png`)
if (existsSync(filename)) {
console.log(`Screenshot for ${template.name} already exists, skipping`)
continue
}
console.log(`Generating screenshot for Template ${template.name} hitting ${url}...`)
try {
await captureWebsite.file(url, filename, {
...(template.screenshotOptions || {}),
launchOptions: { headless: true }
})
console.log(`Screenshot for ${template.name} generated successfully`)
} catch (error) {
console.error(`Error generating screenshot for ${template.name}:`, error)
}
}
}
})
})

56
docs/modules/showcase.ts Normal file
View File

@@ -0,0 +1,56 @@
import { defineNuxtModule } from '@nuxt/kit'
import { existsSync } from 'node:fs'
import { join } from 'pathe'
import captureWebsite from 'capture-website'
interface ContentFile {
id?: string
body?: {
items: TemplateItem[]
}
}
interface TemplateItem {
name: string
url?: string
screenshotUrl?: string
screenshotOptions?: Record<string, any>
}
export default defineNuxtModule((_, nuxt) => {
nuxt.hook('content:file:afterParse', async ({ content: file }: { content: ContentFile }) => {
if (!file.id?.includes('showcase')) {
return
}
if (!file.body?.items?.length) {
return
}
for (const template of file.body.items) {
const url = template.screenshotUrl || template.url
if (!url) {
console.error(`Template ${template.name} has no "url" or "screenshotUrl" to take a screenshot from`)
continue
}
const name = template.name.toLowerCase().replace(/\s/g, '-')
const filename = join(process.cwd(), 'docs/public/assets/showcase', `${name}.png`)
if (existsSync(filename)) {
continue
}
console.log(`Generating screenshot for Template ${template.name} hitting ${url}...`)
try {
await captureWebsite.file(url, filename, {
...(template.screenshotOptions || {}),
launchOptions: { headless: true }
})
console.log(`Screenshot for ${template.name} generated successfully`)
} catch (error) {
console.error(`Error generating screenshot for ${template.name}:`, error)
}
}
})
})

Binary file not shown.

After

Width:  |  Height:  |  Size: 422 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 518 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 749 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 575 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB