Merge remote-tracking branch 'origin/v3' into feat/update-showcase

This commit is contained in:
HugoRCD
2025-04-03 13:12:53 +02:00
13 changed files with 117 additions and 87 deletions

View File

@@ -22,8 +22,20 @@ onMounted(() => {
const navigation = inject<Ref<ContentNavigationItem[]>>('navigation')
const githubLink = computed(() => {
return `https://github.com/nuxt/${value.value}`
})
const desktopLinks = computed(() => props.links.map(({ icon, ...link }) => link))
const mobileLinks = computed(() => props.links.map(link => ({ ...link, defaultOpen: link.children && route.path.startsWith(link.to as string) })))
const mobileLinks = computed(() => [
...props.links.map(link => ({ ...link, defaultOpen: link.children && route.path.startsWith(link.to as string) })),
{
label: 'Open on GitHub',
to: githubLink.value,
icon: 'i-simple-icons-github',
target: '_blank'
}
])
</script>
<template>
@@ -73,7 +85,7 @@ const mobileLinks = computed(() => props.links.map(link => ({ ...link, defaultOp
:key="value"
color="neutral"
variant="ghost"
:to="`https://github.com/nuxt/${value}`"
:to="githubLink"
target="_blank"
icon="i-simple-icons-github"
aria-label="GitHub"

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="38.5957" cy="37.4551" r="1.5" fill="var(--ui-border-accented)" />
</svg>
<UIcon :name="feature.icon" class="size-5 flex-shrink-0" />
<UIcon :name="feature.icon" class="size-5 shrink-0" />
</div>
<div class="flex flex-col">
<h2 class="font-medium text-(--ui-text-highlighted) inline-flex items-center gap-x-1">
{{ 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>
<p class="text-sm text-(--ui-text-muted)">
{{ 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 class="border border-(--ui-border) bg-(--ui-bg)">
<ul class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 items-start justify-center">
<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 divide-y divide-x divide-(--ui-border)">
<li
v-for="item in page.items"
: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">
<span class="sr-only">Go to {{ item.name }}</span>
@@ -51,33 +51,34 @@ useSeoMeta({
<NuxtImg
:src="`/assets/showcase/${item.name.toLowerCase().replace(/\s/g, '-')}.png`"
:alt="`Screenshot of ${item.name}`"
width="311"
height="194"
class="rounded-[calc(var(--ui-radius)*1.5)] group-hover:scale-103 duration-200 object-cover w-full transition-transform pointer-events-none"
width="327"
height="184"
: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 justify-between">
<div class="flex items-center gap-1 px-1 flex-1 min-w-0">
<span class="font-medium text-(--ui-text-highlighted) truncate">
{{ item.name }}
</span>
<UIcon 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 text-(--ui-text-muted)" />
</div>
<UTooltip v-if="item.github" text="Open repository" :content="{ side: 'top' }">
<UButton
:to="item.github"
icon="i-simple-icons-github"
variant="ghost"
color="neutral"
size="xs"
target="_blank"
class="opacity-0 group-hover:opacity-100 transition-all duration-200"
/>
</UTooltip>
<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="text-sm text-(--ui-text-highlighted) font-medium">
{{ item.name }}
</span>
<UIcon name="i-lucide-arrow-up-right" class="size-4 shrink-0" />
</div>
</li>
</ul>
</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>
</UMain>
</template>

View File

@@ -17,7 +17,7 @@ Use the `v-model` directive to control the value of the RadioGroup or the `defau
### Items
Use the `items` prop as an array of strings, numbers or booleans:
Use the `items` prop as an array of strings or numbers:
::component-code
---

View File

@@ -5,16 +5,31 @@ hero:
title: Showcase
description: Discover our selection of projects built with Nuxt UI.
items:
- name: Ovatu
url: https://ovatu.com/
- name: Shelve
url: https://shelve.cloud
github: https://github.com/hugorcd/shelve
- name: Uneed
url: https://uneed.best
url: https://uneed.best/
- name: Details
url: https://details.team
url: https://details.team/
- name: Espace Asso by Benevolt
url: https://asso.benevolt.fr
url: https://asso.benevolt.fr/
- name: Directus Docs
url: https://docs.directus.io
url: https://docs.directus.io/
- name: Super SaaS
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/
url: https://supersaas.dev
- name: CareerDeck
url: https://careerdeck.ai

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