mirror of
https://github.com/ArthurDanjou/ui.git
synced 2026-01-14 12:14:41 +01:00
feat: init blog
This commit is contained in:
@@ -107,6 +107,10 @@ export function useLinks() {
|
||||
to: 'https://github.com/Justineo/tempad-dev-plugin-nuxt-ui',
|
||||
target: '_blank'
|
||||
}]
|
||||
}, {
|
||||
label: 'Blog',
|
||||
icon: 'i-lucide-file-text',
|
||||
to: '/blog'
|
||||
}, {
|
||||
label: 'Releases',
|
||||
icon: 'i-lucide-rocket',
|
||||
|
||||
@@ -57,6 +57,10 @@ export function useSearchLinks() {
|
||||
description: 'Meet the team behind Nuxt UI.',
|
||||
icon: 'i-lucide-users',
|
||||
to: '/team'
|
||||
}, {
|
||||
label: 'Blog',
|
||||
icon: 'i-lucide-file-text',
|
||||
to: '/blog'
|
||||
}, {
|
||||
label: 'Releases',
|
||||
icon: 'i-lucide-rocket',
|
||||
|
||||
4
docs/app/pages/blog/.blog.yml
Normal file
4
docs/app/pages/blog/.blog.yml
Normal file
@@ -0,0 +1,4 @@
|
||||
title: Nuxt UI Blog
|
||||
navigation.title: Blog
|
||||
description: 'Read the latest news, tutorials, and updates about Nuxt UI.'
|
||||
navigation.icon: i-lucide-newspaper
|
||||
111
docs/app/pages/blog/[...slug].vue
Normal file
111
docs/app/pages/blog/[...slug].vue
Normal file
@@ -0,0 +1,111 @@
|
||||
<script setup lang="ts">
|
||||
import type { ContentNavigationItem } from '@nuxt/content'
|
||||
import { findPageBreadcrumb, mapContentNavigation } from '#ui-pro/utils/content'
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
const { data: page } = await useAsyncData(route.path, () =>
|
||||
queryCollection('blog').path(route.path).first()
|
||||
)
|
||||
if (!page.value) throw createError({ statusCode: 404, statusMessage: 'Page not found', fatal: true })
|
||||
const { data: surround } = await useAsyncData(`${route.path}-surround`, () =>
|
||||
queryCollectionItemSurroundings('blog', route.path, {
|
||||
fields: ['description']
|
||||
})
|
||||
)
|
||||
|
||||
const navigation = inject<Ref<ContentNavigationItem[]>>('navigation', ref([]))
|
||||
const blogNavigation = computed(() => navigation.value.find(item => item.path === '/blog')?.children || [])
|
||||
|
||||
const breadcrumb = computed(() => mapContentNavigation(findPageBreadcrumb(blogNavigation?.value, page.value)).map(({ icon, ...link }) => link))
|
||||
|
||||
if (page.value.image) {
|
||||
defineOgImage({ url: page.value.image })
|
||||
} else {
|
||||
defineOgImageComponent('Blog', {
|
||||
headline: breadcrumb.value.map(item => item.label).join(' > ')
|
||||
}, {
|
||||
fonts: ['Geist:400', 'Geist:600']
|
||||
})
|
||||
}
|
||||
|
||||
const title = page.value?.seo?.title || page.value?.title
|
||||
const description = page.value?.seo?.description || page.value?.description
|
||||
|
||||
useSeoMeta({
|
||||
title,
|
||||
description,
|
||||
ogDescription: description,
|
||||
ogTitle: title
|
||||
})
|
||||
|
||||
const articleLink = computed(() => `${window?.location}`)
|
||||
|
||||
const formatDate = (dateString: Date) => {
|
||||
return new Date(dateString).toLocaleDateString('en-US', {
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric'
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UMain class="mt-20 px-2">
|
||||
<UContainer class="relative min-h-screen">
|
||||
<UPage v-if="page">
|
||||
<ULink
|
||||
to="/blog"
|
||||
class="text-sm flex items-center gap-1"
|
||||
>
|
||||
<UIcon name="lucide:chevron-left" />
|
||||
Blog
|
||||
</ULink>
|
||||
<div class="flex flex-col gap-3 mt-8">
|
||||
<div class="flex text-xs text-muted items-center justify-center gap-2">
|
||||
<span v-if="page.date">
|
||||
{{ formatDate(page.date) }}
|
||||
</span>
|
||||
<span v-if="page.date && page.minRead">
|
||||
-
|
||||
</span>
|
||||
<span v-if="page.minRead">
|
||||
{{ page.minRead }} MIN READ
|
||||
</span>
|
||||
</div>
|
||||
<NuxtImg
|
||||
:src="page.image"
|
||||
:alt="page.title"
|
||||
class="rounded-lg w-full h-[400px] object-cover object-center max-w-5xl mx-auto"
|
||||
/>
|
||||
<h1 class="text-4xl text-center font-medium max-w-3xl mx-auto mt-4">
|
||||
{{ page.title }}
|
||||
</h1>
|
||||
<p class="text-muted text-center max-w-2xl mx-auto">
|
||||
{{ page.description }}
|
||||
</p>
|
||||
<div class="mt-4 flex justify-center flex-wrap items-center gap-6">
|
||||
<UUser v-for="(author, index) in page.authors" :key="index" v-bind="author" :description="author.to ? `@${author.to.split('/').pop()}` : undefined" />
|
||||
</div>
|
||||
</div>
|
||||
<UPageBody class="max-w-3xl mx-auto">
|
||||
<ContentRenderer
|
||||
v-if="page.body"
|
||||
:value="page"
|
||||
/>
|
||||
|
||||
<div class="flex items-center justify-end gap-2 text-sm text-muted">
|
||||
<UButton
|
||||
size="sm"
|
||||
variant="link"
|
||||
color="neutral"
|
||||
label="Copy link"
|
||||
@click="copyToClipboard(articleLink, 'Article link copied to clipboard')"
|
||||
/>
|
||||
</div>
|
||||
<UContentSurround :surround />
|
||||
</UPageBody>
|
||||
</UPage>
|
||||
</UContainer>
|
||||
</UMain>
|
||||
</template>
|
||||
57
docs/app/pages/blog/index.vue
Normal file
57
docs/app/pages/blog/index.vue
Normal file
@@ -0,0 +1,57 @@
|
||||
<script setup lang="ts">
|
||||
// @ts-expect-error yaml is not typed
|
||||
import page from '.blog.yml'
|
||||
|
||||
const { data: posts } = await useAsyncData('blogs', () =>
|
||||
queryCollection('blog').order('date', 'DESC').all()
|
||||
)
|
||||
|
||||
useSeoMeta({
|
||||
titleTemplate: `%s`,
|
||||
title: page.title,
|
||||
description: page.description,
|
||||
ogTitle: `${page.title}`,
|
||||
ogDescription: page.description
|
||||
})
|
||||
|
||||
/* defineOgImageComponent('Docs', {
|
||||
headline: 'Blog',
|
||||
title: page.title,
|
||||
description: page.description
|
||||
}) */
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UContainer v-if="page">
|
||||
<UPageHero
|
||||
:title="page.title"
|
||||
:description="page.description"
|
||||
orientation="horizontal"
|
||||
/>
|
||||
|
||||
<UPageBody>
|
||||
<UContainer>
|
||||
<UBlogPosts class="mb-12 md:grid-cols-2 lg:grid-cols-3">
|
||||
<UBlogPost
|
||||
v-for="(article, index) in posts"
|
||||
:key="article.path"
|
||||
:to="article.path"
|
||||
:title="article.title"
|
||||
:description="article.description"
|
||||
:image="{
|
||||
src: article.image,
|
||||
width: (index === 0 ? 672 : 437),
|
||||
height: (index === 0 ? 378 : 246),
|
||||
alt: `${article.title} image`
|
||||
}"
|
||||
:authors="article.authors?.map(author => ({ ...author, avatar: { ...author.avatar, alt: `${author.name} avatar` } }))"
|
||||
:badge="{ label: article.category, color: 'primary', variant: 'subtle' }"
|
||||
:variant="index === 0 ? 'outline' : 'subtle'"
|
||||
:orientation="index === 0 ? 'horizontal' : 'vertical'"
|
||||
:class="[index === 0 && 'col-span-full']"
|
||||
/>
|
||||
</UBlogPosts>
|
||||
</UContainer>
|
||||
</UPageBody>
|
||||
</UContainer>
|
||||
</template>
|
||||
@@ -13,6 +13,22 @@ const Button = z.object({
|
||||
target: z.enum(['_blank', '_self']).optional()
|
||||
})
|
||||
|
||||
const Image = z.object({
|
||||
src: z.string(),
|
||||
alt: z.string(),
|
||||
width: z.number().optional(),
|
||||
height: z.number().optional()
|
||||
})
|
||||
|
||||
const Author = z.object({
|
||||
name: z.string(),
|
||||
description: z.string().optional(),
|
||||
username: z.string().optional(),
|
||||
twitter: z.string().optional(),
|
||||
to: z.string().optional(),
|
||||
avatar: Image.optional()
|
||||
})
|
||||
|
||||
const schema = z.object({
|
||||
category: z.enum(['layout', 'form', 'element', 'navigation', 'data', 'overlay']).optional(),
|
||||
framework: z.string().optional(),
|
||||
@@ -75,5 +91,17 @@ export const collections = {
|
||||
})
|
||||
}))
|
||||
})
|
||||
}),
|
||||
blog: defineCollection({
|
||||
type: 'page',
|
||||
source: 'blog/*',
|
||||
schema: z.object({
|
||||
image: z.string().editor({ input: 'media' }),
|
||||
authors: z.array(Author),
|
||||
date: z.string().date(),
|
||||
draft: z.boolean().optional(),
|
||||
category: z.enum(['Release', 'Tutorial', 'Announcement', 'Article']),
|
||||
tags: z.array(z.string())
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
197
docs/content/blog/nuxt-ui-v3.md
Normal file
197
docs/content/blog/nuxt-ui-v3.md
Normal file
@@ -0,0 +1,197 @@
|
||||
---
|
||||
title: Nuxt UI v3
|
||||
description: Nuxt UI v3 is out! After 1500+ commits, this major redesign brings
|
||||
improved accessibility, Tailwind CSS v4 support, and full Vue compatibility
|
||||
navigation: false
|
||||
image: /assets/blog/nuxt-ui-v3.png
|
||||
authors:
|
||||
- name: Benjamin Canac
|
||||
avatar:
|
||||
src: https://github.com/benjamincanac.png
|
||||
to: https://x.com/benjamincanac
|
||||
- name: Sébastien Chopin
|
||||
avatar:
|
||||
src: https://github.com/atinux.png
|
||||
to: https://x.com/atinux
|
||||
- name: Hugo Richard
|
||||
avatar:
|
||||
src: https://github.com/hugorcd.png
|
||||
to: https://x.com/hugorcd__
|
||||
date: 2025-03-12T10:00:00.000Z
|
||||
category: Release
|
||||
---
|
||||
|
||||
We are thrilled to announce the release of Nuxt UI v3, a complete redesign of our UI library that brings significant improvements in accessibility, performance, and developer experience. This major update represents over 1500 commits of hard work, collaboration, and innovation from our team and the community.
|
||||
|
||||
## 🚀 Reimagined from the Ground Up
|
||||
|
||||
Nuxt UI v3 represents a major leap forward in our journey to provide the most comprehensive UI solution for Vue and Nuxt developers. This version has been rebuilt from the ground up with modern technologies and best practices in mind.
|
||||
|
||||
### **From HeadlessUI to Reka UI**
|
||||
|
||||
With Reka UI at its core, Nuxt UI v3 delivers:
|
||||
|
||||
• Proper keyboard navigation across all interactive components
|
||||
|
||||
• ARIA attributes automatically handled for you
|
||||
|
||||
• Focus management that just works
|
||||
|
||||
• Screen reader friendly components out of the box
|
||||
|
||||
This means you can build applications that work for everyone without becoming an accessibility expert.
|
||||
|
||||
### **Tailwind CSS v4 Integration**
|
||||
|
||||
The integration with Tailwind CSS v4 brings huge performance improvements:
|
||||
|
||||
• **5x faster runtime** with optimized component rendering
|
||||
|
||||
• **100x faster build times** thanks to the new CSS-first engine
|
||||
|
||||
• Smaller bundle sizes with more efficient styling
|
||||
|
||||
Your applications will feel snappier, build quicker, and load faster for your users.
|
||||
|
||||
## 🎨 A Brand New Design System
|
||||
|
||||
```html
|
||||
<!-- Before: Inconsistent color usage with duplicate dark mode classes -->
|
||||
<div class="bg-gray-100 dark:bg-gray-800 p-4 rounded-lg">
|
||||
<h2 class="text-gray-900 dark:text-white text-xl mb-2">User Profile</h2>
|
||||
<p class="text-gray-600 dark:text-gray-300">Account settings and preferences</p>
|
||||
<button class="bg-blue-500 text-white px-3 py-1 rounded mt-2">Edit Profile</button>
|
||||
</div>
|
||||
```
|
||||
|
||||
```html
|
||||
<!-- After: Semantic design tokens with automatic dark mode support -->
|
||||
<div class="bg-muted p-4 rounded-lg">
|
||||
<h2 class="text-highlighted text-xl mb-2">User Profile</h2>
|
||||
<p class="text-muted">Account settings and preferences</p>
|
||||
<UButton color="primary" size="sm" class="mt-2">Edit Profile</UButton>
|
||||
</div>
|
||||
```
|
||||
|
||||
Our new color system includes 7 semantic color aliases:
|
||||
|
||||
| Color | Default | Description |
|
||||
|-----------------------------------|----------|--------------------------------------------------|
|
||||
| :code[primary]{.text-primary} | `blue` | Primary color to represent the brand.
|
||||
| :code[secondary]{.text-secondary} | `blue` | Secondary color to complement the primary color.
|
||||
| :code[success]{.text-success} | `green` | Used for success states.
|
||||
| :code[info]{.text-info} | `blue` | Used for informational states.
|
||||
| :code[warning]{.text-warning} | `yellow` | Used for warning states.
|
||||
| :code[error]{.text-error} | `red` | Used for form error validation states. |
|
||||
| `neutral` | `slate` | Neutral color for backgrounds, text, etc. |
|
||||
|
||||
This approach makes your codebase more maintainable and your UI more consistent—especially when working in teams. With these semantic tokens, light and dark mode transitions become effortless, as the system automatically handles the appropriate color values for each theme without requiring duplicate class definitions.
|
||||
|
||||
## 💚 Complete Vue Compatibility
|
||||
|
||||
We're really happy to expand the scope of Nuxt UI beyond the Nuxt framework. With v3, both Nuxt UI and Nuxt UI Pro now work seamlessly in any Vue project, this means you can:
|
||||
|
||||
• Use the same components across all your Vue projects
|
||||
|
||||
• Benefit from Nuxt UI's theming system in any Vue application
|
||||
|
||||
• Enjoy auto-imports and TypeScript support outside of Nuxt
|
||||
|
||||
• Leverage both basic components and advanced Pro components in any Vue project
|
||||
|
||||
```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()
|
||||
]
|
||||
})
|
||||
```
|
||||
|
||||
## 📦 Components for Every Need
|
||||
|
||||
With 54 core components, 50 Pro components, and 42 Prose components, Nuxt UI v3 provides solutions for virtually any UI challenge:
|
||||
|
||||
• **Data Display**: Tables, charts, and visualizations that adapt to your data
|
||||
|
||||
• **Navigation**: Menus, tabs, and breadcrumbs that guide users intuitively
|
||||
|
||||
• **Feedback**: Toasts, alerts, and modals that communicate clearly
|
||||
|
||||
• **Forms**: Inputs, selectors, and validation that simplify data collection
|
||||
|
||||
• **Layout**: Grids, containers, and responsive systems that organize content beautifully
|
||||
|
||||
Each component is designed to be both beautiful out of the box and deeply customizable when needed.
|
||||
|
||||
## 🔷 Improved TypeScript Integration
|
||||
|
||||
We've completely revamped our TypeScript integration, with features that make you more productive:
|
||||
|
||||
- Complete type safety with helpful autocompletion
|
||||
- Generic-based components for flexible APIs
|
||||
- Type-safe theming through a clear, consistent API
|
||||
|
||||
```ts
|
||||
export default defineAppConfig({
|
||||
ui: {
|
||||
button: {
|
||||
// Your IDE will show all available options
|
||||
slots: {
|
||||
base: 'font-bold rounded-lg'
|
||||
},
|
||||
defaultVariants: {
|
||||
size: 'md',
|
||||
color: 'error'
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## ⬆️ Upgrading to v3
|
||||
|
||||
We've prepared a comprehensive [migration](https://ui.nuxt.com/getting-started/migration) guide to help you upgrade from v2 to v3. While there are breaking changes due to our complete overhaul, we've worked hard to make the transition as smooth as possible.
|
||||
|
||||
## 🎯 Getting Started
|
||||
|
||||
Whether you're starting a new project or upgrading an existing one, getting started with Nuxt UI v3 is easy:
|
||||
|
||||
```bash
|
||||
# Create a new Nuxt project with Nuxt UI
|
||||
npx nuxi@latest init my-app -t ui
|
||||
```
|
||||
|
||||
::code-group{sync="pm"}
|
||||
```bash [pnpm]
|
||||
pnpm add @nuxt/ui@latest
|
||||
```
|
||||
|
||||
```bash [yarn]
|
||||
yarn add @nuxt/ui@latest
|
||||
```
|
||||
|
||||
```bash [npm]
|
||||
npm install @nuxt/ui@latest
|
||||
```
|
||||
|
||||
```bash [bun]
|
||||
bun add @nuxt/ui@latest
|
||||
```
|
||||
::
|
||||
|
||||
::warning
|
||||
If you're using **pnpm**, ensure that you either set [`shamefully-hoist=true`](https://pnpm.io/npmrc#shamefully-hoist) in your `.npmrc` file or install `tailwindcss` in your project's root directory.
|
||||
::
|
||||
|
||||
Visit our [documentation](https://ui.nuxt.com/getting-started) to explore all the components and features available in Nuxt UI v3.
|
||||
|
||||
## 🙏 Thank You
|
||||
|
||||
This release represents thousands of hours of work from our team and the community. We'd like to thank everyone who contributed to making Nuxt UI v3 a reality.
|
||||
|
||||
We're excited to see what you'll build with Nuxt UI v3!
|
||||
BIN
docs/public/assets/blog/nuxt-ui-v3.png
Normal file
BIN
docs/public/assets/blog/nuxt-ui-v3.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 515 KiB |
Reference in New Issue
Block a user