mirror of
https://github.com/ArthurDanjou/ui.git
synced 2026-01-14 12:14:41 +01:00
docs: add landing page (#3448)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
const route = useRoute()
|
||||
|
||||
const links = [{
|
||||
label: 'Figma',
|
||||
to: '/figma'
|
||||
@@ -16,7 +18,7 @@ const links = [{
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<USeparator icon="i-simple-icons-nuxtdotjs" class="h-px" />
|
||||
<USeparator :icon="route.path === '/' ? undefined : 'i-simple-icons-nuxtdotjs'" class="h-px" />
|
||||
|
||||
<UFooter>
|
||||
<template #left>
|
||||
|
||||
91
docs/app/components/SkyBg.vue
Normal file
91
docs/app/components/SkyBg.vue
Normal file
@@ -0,0 +1,91 @@
|
||||
<script setup lang="ts">
|
||||
interface Star {
|
||||
x: number
|
||||
y: number
|
||||
size: number
|
||||
twinkleDelay: number
|
||||
id: string
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
starCount?: number
|
||||
color?: string
|
||||
size?: { min: number, max: number }
|
||||
speed?: 'slow' | 'normal' | 'fast'
|
||||
}>(), {
|
||||
starCount: 50,
|
||||
color: 'var(--ui-primary)',
|
||||
size: () => ({
|
||||
min: 1,
|
||||
max: 3
|
||||
}),
|
||||
speed: 'normal'
|
||||
})
|
||||
|
||||
// Generate random stars
|
||||
const generateStars = (count: number): Star[] => {
|
||||
return Array.from({ length: count }, () => {
|
||||
const x = Math.floor(Math.random() * 100)
|
||||
const y = Math.floor(Math.random() * 100)
|
||||
const size = Math.random() * (props.size.max - props.size.min) + props.size.min
|
||||
const twinkleDelay = Math.random() * 5
|
||||
|
||||
return { x, y, size, twinkleDelay, id: Math.random().toString(36).substring(2, 9) }
|
||||
})
|
||||
}
|
||||
|
||||
// Generate all stars
|
||||
const stars = ref<Star[]>(generateStars(props.starCount))
|
||||
|
||||
// Compute twinkle animation duration based on speed
|
||||
const twinkleDuration = computed(() => {
|
||||
const speedMap: Record<string, string> = {
|
||||
slow: '4s',
|
||||
normal: '2s',
|
||||
fast: '1s'
|
||||
}
|
||||
return speedMap[props.speed]
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="absolute pointer-events-none z-[-1] inset-y-0 left-4 right-4 lg:right-[50%] overflow-hidden">
|
||||
<ClientOnly>
|
||||
<div
|
||||
v-for="star in stars"
|
||||
:key="star.id"
|
||||
class="star absolute"
|
||||
:style="{
|
||||
'left': `${star.x}%`,
|
||||
'top': `${star.y}%`,
|
||||
'transform': 'translate(-50%, -50%)',
|
||||
'--star-size': `${star.size}px`,
|
||||
'--star-color': color,
|
||||
'--twinkle-delay': `${star.twinkleDelay}s`,
|
||||
'--twinkle-duration': twinkleDuration
|
||||
}"
|
||||
/>
|
||||
</ClientOnly>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.star {
|
||||
width: var(--star-size);
|
||||
height: var(--star-size);
|
||||
background-color: var(--star-color);
|
||||
border-radius: 50%;
|
||||
animation: twinkle var(--twinkle-duration) ease-in-out infinite;
|
||||
animation-delay: var(--twinkle-delay);
|
||||
will-change: opacity;
|
||||
}
|
||||
|
||||
@keyframes twinkle {
|
||||
0%, 100% {
|
||||
opacity: 0.2;
|
||||
}
|
||||
50% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
File diff suppressed because one or more lines are too long
139
docs/app/components/home/HomeContributors.vue
Normal file
139
docs/app/components/home/HomeContributors.vue
Normal file
@@ -0,0 +1,139 @@
|
||||
<script setup lang="ts">
|
||||
const props = withDefaults(defineProps<{
|
||||
contributors?: {
|
||||
username: string
|
||||
}[]
|
||||
level?: number
|
||||
max?: number
|
||||
paused?: boolean
|
||||
}>(), {
|
||||
level: 0,
|
||||
max: 4,
|
||||
paused: false
|
||||
})
|
||||
|
||||
const contributors = computed(() => props.contributors?.slice(0, 5) ?? [])
|
||||
|
||||
const el = ref(null)
|
||||
const { width } = useElementSize(el)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="isolate rounded-full relative circle w-full aspect-[1/1] p-8 sm:p-12 md:p-14 lg:p-10 xl:p-16 before:absolute before:inset-px before:bg-(--ui-bg) before:rounded-full z-(--level)"
|
||||
:class="{ 'animation-paused': paused }"
|
||||
:style="{
|
||||
'--duration': `${((level + 1) * 8)}s`,
|
||||
'--level': level + 1
|
||||
}"
|
||||
>
|
||||
<HomeContributors
|
||||
v-if="(level + 1) < max"
|
||||
:max="max"
|
||||
:level="level + 1"
|
||||
:contributors="props.contributors?.slice(5) ?? []"
|
||||
:paused="paused"
|
||||
/>
|
||||
|
||||
<div
|
||||
ref="el"
|
||||
class="avatars absolute inset-0 grid"
|
||||
:style="{
|
||||
'--total': contributors.length,
|
||||
'--offset': `${width / 2}px`
|
||||
}"
|
||||
>
|
||||
<UTooltip
|
||||
v-for="(contributor, index) in contributors"
|
||||
:key="contributor.username"
|
||||
:text="contributor.username"
|
||||
:delay-duration="0"
|
||||
>
|
||||
<NuxtLink
|
||||
:to="`https://github.com/${contributor.username}`"
|
||||
:aria-label="contributor.username"
|
||||
target="_blank"
|
||||
class="avatar flex absolute top-1/2 left-1/2"
|
||||
tabindex="-1"
|
||||
:style="{
|
||||
'--index': index + 1
|
||||
}"
|
||||
>
|
||||
<img
|
||||
width="56"
|
||||
height="56"
|
||||
:src="`https://ipx.nuxt.com/s_56x56/gh_avatar/${contributor.username}`"
|
||||
:srcset="`https://ipx.nuxt.com/s_112x112/gh_avatar/${contributor.username} 2x`"
|
||||
:alt="contributor.username"
|
||||
class="ring-2 ring-(--ui-border) lg:hover:ring-(--ui-border-inverted) transition rounded-full size-7"
|
||||
loading="lazy"
|
||||
>
|
||||
</NuxtLink>
|
||||
</UTooltip>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.circle:after {
|
||||
--start: 0deg;
|
||||
--end: 360deg;
|
||||
--border-color: var(--ui-border);
|
||||
--highlight-color: var(--ui-color-neutral-400);
|
||||
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
margin: -1px;
|
||||
opacity: 1;
|
||||
border-radius: 9999px;
|
||||
z-index: -1;
|
||||
background: var(--border-color);
|
||||
|
||||
@supports (background: paint(houdini)) {
|
||||
background: linear-gradient(var(--angle), var(--border-color), var(--border-color), var(--border-color), var(--border-color), var(--highlight-color));
|
||||
animation: var(--duration) rotate linear infinite;
|
||||
}
|
||||
}
|
||||
|
||||
.dark .circle:after {
|
||||
--highlight-color: var(--color-white);
|
||||
}
|
||||
|
||||
.animation-paused.circle:after,
|
||||
.animation-paused .avatars {
|
||||
animation-play-state: paused;
|
||||
}
|
||||
|
||||
.avatars {
|
||||
--start: calc(var(--level) * 36deg);
|
||||
--end: calc(360deg + (var(--level) * 36deg));
|
||||
transform: rotate(var(--angle));
|
||||
animation: calc(var(--duration) + 60s) rotate linear infinite;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
--deg: calc(var(--index) * (360deg / var(--total)));
|
||||
--transformX: calc(cos(var(--deg)) * var(--offset));
|
||||
--transformY: calc(sin(var(--deg)) * var(--offset));
|
||||
transform: translate(calc(-50% + var(--transformX)), calc(-50% + var(--transformY))) rotate(calc(360deg - var(--angle)));
|
||||
}
|
||||
|
||||
@keyframes rotate {
|
||||
from {
|
||||
--angle: var(--start);
|
||||
}
|
||||
to {
|
||||
--angle: var(--end);
|
||||
}
|
||||
}
|
||||
|
||||
@property --angle {
|
||||
syntax: '<angle>';
|
||||
initial-value: 0deg;
|
||||
inherits: true;
|
||||
}
|
||||
</style>
|
||||
@@ -12,23 +12,17 @@ export function useLinks() {
|
||||
to: '/components',
|
||||
active: route.path === '/components',
|
||||
children: [{
|
||||
label: 'Layout',
|
||||
to: '/components#layout',
|
||||
description: 'Container, grid, divider and responsive layout.',
|
||||
icon: 'i-lucide-layout',
|
||||
active: route.fullPath === '/components#layout'
|
||||
label: 'Element',
|
||||
to: '/components#element',
|
||||
description: 'Button, badge, icon, alert, and small UI elements.',
|
||||
icon: 'i-lucide-mouse-pointer',
|
||||
active: route.fullPath === '/components#element'
|
||||
}, {
|
||||
label: 'Form',
|
||||
to: '/components#form',
|
||||
description: 'Input, select, checkbox, radio and form validation.',
|
||||
icon: 'i-lucide-text-cursor-input',
|
||||
active: route.fullPath === '/components#form'
|
||||
}, {
|
||||
label: 'Element',
|
||||
to: '/components#element',
|
||||
description: 'Button, badge, icon, alert, and small UI elements.',
|
||||
icon: 'i-lucide-mouse-pointer',
|
||||
active: route.fullPath === '/components#element'
|
||||
}, {
|
||||
label: 'Data',
|
||||
to: '/components#data',
|
||||
@@ -47,6 +41,12 @@ export function useLinks() {
|
||||
description: 'Modal, tooltip, dialog and popover.',
|
||||
icon: 'i-lucide-layers',
|
||||
active: route.fullPath === '/components#overlay'
|
||||
}, {
|
||||
label: 'Layout',
|
||||
to: '/components#layout',
|
||||
description: 'Container, grid, divider and responsive layout.',
|
||||
icon: 'i-lucide-layout',
|
||||
active: route.fullPath === '/components#layout'
|
||||
}]
|
||||
}, {
|
||||
label: 'Pro',
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
<template>
|
||||
<div>
|
||||
<slot />
|
||||
</div>
|
||||
<slot />
|
||||
</template>
|
||||
|
||||
172
docs/app/pages/.index.yml
Normal file
172
docs/app/pages/.index.yml
Normal file
@@ -0,0 +1,172 @@
|
||||
title: The Intuitive Vue UI Library
|
||||
description: Create beautiful, responsive & accessible web apps quickly with Vue or Nuxt. Nuxt UI is an open-source UI library of 50+ customizable components built with Tailwind CSS and Reka UI.
|
||||
hero:
|
||||
title: The Intuitive Vue UI Library
|
||||
description: Create beautiful, responsive & accessible web apps quickly with Vue or Nuxt. Nuxt UI is an open-source UI library of 50+ customizable components built with Tailwind CSS and Reka UI.
|
||||
links:
|
||||
- label: Get started
|
||||
to: /getting-started/installation
|
||||
- label: Explore components
|
||||
to: /components
|
||||
variant: outline
|
||||
color: neutral
|
||||
trailingIcon: i-lucide-arrow-right
|
||||
features:
|
||||
- icon: i-logos-tailwindcss-icon
|
||||
title: Styled with Tailwind CSS v4
|
||||
description: Beautifully styled by default, overwrite any style you want.
|
||||
- icon: i-custom-reka-ui
|
||||
title: Accessible with Reka UI
|
||||
description: Robust accessibility out of the box.
|
||||
- icon: i-logos-typescript-icon
|
||||
title: Type-safe with TypeScript
|
||||
description: Auto-complete and type safety for all components.
|
||||
features:
|
||||
- title: Build for the modern web
|
||||
description: Powered by Tailwind CSS v4 and Reka UI for top performance and accessibility.
|
||||
icon: i-lucide-rocket
|
||||
to: /getting-started
|
||||
- title: Flexible design system
|
||||
description: Beautiful by default and easily customizable with design tokens to your brand.
|
||||
icon: i-lucide-swatch-book
|
||||
to: /getting-started/theme#design-system
|
||||
- title: Internationalization (i18n)
|
||||
description: Nuxt UI is translated into 30+ languages, works well with i18n and multi-directional support (LTR/RTL).
|
||||
icon: i-lucide-globe
|
||||
to: /getting-started/i18n/nuxt
|
||||
- title: Easy font customization
|
||||
description: Performance-optimized fonts with first-class @nuxt/fonts integration.
|
||||
icon: i-lucide-a-large-small
|
||||
to: /getting-started/fonts
|
||||
- title: Large icons sets
|
||||
description: Access to over 200,000 customizable icons from Iconify, seamlessly integrated with Iconify.
|
||||
icon: i-lucide-smile
|
||||
to: /getting-started/icons
|
||||
- title: Light & Dark
|
||||
description: Dark mode-ready components, seamless integration with @nuxtjs/color-mode.
|
||||
icon: i-lucide-sun-moon
|
||||
to: /getting-started/color-mode/nuxt
|
||||
design_system:
|
||||
title: Flexible design system
|
||||
description: Build your next project faster with Nuxt UI's comprehensive design system. Featuring semantic color aliases, comprehensive design tokens, and automatic light/dark mode support for accessible components out of the box.
|
||||
links:
|
||||
- label: Learn more
|
||||
to: /getting-started/theme#design-system
|
||||
variant: outline
|
||||
color: neutral
|
||||
trailingIcon: i-lucide-arrow-right
|
||||
features:
|
||||
- title: Color aliases via AppConfig
|
||||
description: Configure 7 semantic color aliases (primary, secondary, success, info, warning, error, neutral) at runtime through AppConfig without rebuilding your application
|
||||
icon: i-lucide-palette
|
||||
- title: Comprehensive design tokens
|
||||
description: Extensive set of neutral palette tokens for text, backgrounds, and borders with automatic light/dark mode support via CSS variables like --ui-text, --ui-bg, --ui-border
|
||||
icon: i-lucide-component
|
||||
- title: Global style variables
|
||||
description: Customize global styling with --ui-radius for consistent border rounding and --ui-container for layout widths across your entire application
|
||||
icon: i-lucide-ruler
|
||||
code: |
|
||||
::code-group
|
||||
|
||||
```ts [app.config.ts]
|
||||
export default defineAppConfig({
|
||||
ui: {
|
||||
colors: {
|
||||
primary: 'indigo',
|
||||
secondary: 'pink',
|
||||
success: 'green',
|
||||
info: 'blue',
|
||||
warning: 'orange',
|
||||
error: 'red',
|
||||
neutral: 'zinc'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
```
|
||||
|
||||
```css [main.css]
|
||||
@import "tailwindcss" theme(static);
|
||||
@import "@nuxt/ui";
|
||||
|
||||
:root {
|
||||
--ui-radius: var(--radius-sm);
|
||||
--ui-container: 90rem;
|
||||
--ui-bg: var(--ui-color-neutral-50);
|
||||
--ui-text: var(--ui-color-neutral-900);
|
||||
}
|
||||
.dark {
|
||||
--ui-bg: var(--ui-color-neutral-950);
|
||||
--ui-border: var(--ui-color-neutral-900);
|
||||
}
|
||||
```
|
||||
|
||||
::
|
||||
component_customization:
|
||||
title: Powerful component customization
|
||||
description: Nuxt UI leverages [Tailwind Variants](https://www.tailwind-variants.org/) to provide a powerful, maintainable system for managing component styles and intelligently merging Tailwind CSS classes without conflicts.
|
||||
links:
|
||||
- label: Learn more
|
||||
to: /getting-started/theme#customize-theme
|
||||
variant: outline
|
||||
color: neutral
|
||||
trailingIcon: i-lucide-arrow-right
|
||||
features:
|
||||
- title: Powerful slot and variant system
|
||||
description: Customize component parts with slots and apply different styles based on props, creating consistent UI patterns with granular control over styling
|
||||
icon: i-lucide-layout-grid
|
||||
- title: Global theme with AppConfig
|
||||
description: Configure component styles project-wide with a centralized AppConfig that maintains consistency across your application without rebuilding
|
||||
icon: i-lucide-settings-2
|
||||
- title: Per-component customization
|
||||
description: Fine-tune individual components with the ui prop for slot-specific styling and class prop for root element overrides, providing maximum flexibility
|
||||
icon: i-lucide-component
|
||||
code: |
|
||||
::code-group
|
||||
|
||||
```ts [app.config.ts]
|
||||
export default defineAppConfig({
|
||||
ui: {
|
||||
button: {
|
||||
slots: {
|
||||
base: 'group font-bold',
|
||||
trailingIcon: 'group-data-[state=open]:rotate-180 transition-transform duration-200'
|
||||
},
|
||||
defaultVariants: {
|
||||
color: 'neutral',
|
||||
variant: 'subtle'
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
```vue [Collapsible.vue]
|
||||
<template>
|
||||
<UCollapsible>
|
||||
<UButton
|
||||
label="Open"
|
||||
color="neutral"
|
||||
variant="subtle"
|
||||
trailing-icon="i-lucide-chevron-down"
|
||||
:ui="{
|
||||
trailingIcon: 'group-data-[state=open]:rotate-180 transition-transform duration-200'
|
||||
}"
|
||||
class="group font-bold"
|
||||
/>
|
||||
</UCollapsible>
|
||||
</template>
|
||||
```
|
||||
|
||||
::
|
||||
community:
|
||||
title: Nuxt UI open-source community
|
||||
description: Join our thriving community to contribute code, report issues, suggest features, or help with documentation. Every contribution makes Nuxt UI better for everyone.
|
||||
links:
|
||||
- label: Star on GitHub
|
||||
color: neutral
|
||||
variant: outline
|
||||
to: https://github.com/nuxt/ui
|
||||
target: _blank
|
||||
icon: i-lucide-star
|
||||
@@ -15,7 +15,7 @@ useSeoMeta({
|
||||
ogImage: joinURL(url, '/og-image.png')
|
||||
})
|
||||
|
||||
const { data: components } = await useAsyncData('components', () => {
|
||||
const { data: components } = await useAsyncData('all-components', () => {
|
||||
return queryCollection('content')
|
||||
.where('path', 'LIKE', '/components/%')
|
||||
.where('extension', '=', 'md')
|
||||
@@ -31,17 +31,13 @@ const componentsPerCategory = computed(() => {
|
||||
})
|
||||
|
||||
const categories = [{
|
||||
id: 'layout',
|
||||
title: 'Layout',
|
||||
description: 'Structural components for organizing content, including containers, grids, dividers, and responsive layout systems.'
|
||||
id: 'element',
|
||||
title: 'Element',
|
||||
description: 'Core UI building blocks like buttons, badges, icons, avatars, and other fundamental interface elements.'
|
||||
}, {
|
||||
id: 'form',
|
||||
title: 'Form',
|
||||
description: 'Interactive form elements including inputs, selects, checkboxes, radio buttons, and advanced form validation components.'
|
||||
}, {
|
||||
id: 'element',
|
||||
title: 'Element',
|
||||
description: 'Core UI building blocks like buttons, badges, icons, avatars, and other fundamental interface elements.'
|
||||
}, {
|
||||
id: 'data',
|
||||
title: 'Data',
|
||||
@@ -54,6 +50,10 @@ const categories = [{
|
||||
id: 'overlay',
|
||||
title: 'Overlay',
|
||||
description: 'Floating UI elements like modals, dialogs, tooltips, popovers, and other components that overlay the main content.'
|
||||
}, {
|
||||
id: 'layout',
|
||||
title: 'Layout',
|
||||
description: 'Structural components for organizing content, including containers, grids, dividers, and responsive layout systems.'
|
||||
}]
|
||||
|
||||
const { y } = useWindowScroll()
|
||||
@@ -81,7 +81,10 @@ onMounted(() => {
|
||||
orientation="vertical"
|
||||
:ui="{ title: 'text-balance', container: 'relative' }"
|
||||
>
|
||||
<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" />
|
||||
<template #top>
|
||||
<div class="absolute z-[-1] rounded-full bg-(--ui-primary) blur-[300px] size-60 sm:size-80 transform -translate-x-1/2 left-1/2 -translate-y-80" />
|
||||
</template>
|
||||
|
||||
<template #headline>
|
||||
<UButton
|
||||
to="https://tailwindcss.com"
|
||||
@@ -96,6 +99,7 @@ onMounted(() => {
|
||||
<template #title>
|
||||
Build beautiful UI with <span class="text-(--ui-primary)">{{ components!.length }}+</span> powerful components
|
||||
</template>
|
||||
|
||||
<template #links>
|
||||
<UButton
|
||||
to="/getting-started/installation/vue"
|
||||
@@ -114,10 +118,11 @@ onMounted(() => {
|
||||
size="xl"
|
||||
/>
|
||||
</template>
|
||||
<template #top>
|
||||
<div class="absolute z-[-1] rounded-full bg-(--ui-primary) blur-[300px] size-60 sm:size-80 transform -translate-x-1/2 left-1/2 -translate-y-80" />
|
||||
|
||||
<div class="absolute inset-y-0 inset-x-4 sm:inset-x-6 lg:inset-x-8">
|
||||
<StarsBg />
|
||||
</template>
|
||||
</div>
|
||||
<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" />
|
||||
</UPageHero>
|
||||
|
||||
<div v-for="category in categories" :key="category.id">
|
||||
|
||||
307
docs/app/pages/index.vue
Normal file
307
docs/app/pages/index.vue
Normal file
@@ -0,0 +1,307 @@
|
||||
<script setup lang="ts">
|
||||
import { joinURL } from 'ufo'
|
||||
// @ts-expect-error yaml is not typed
|
||||
import page from '.index.yml'
|
||||
|
||||
const { url } = useSiteConfig()
|
||||
|
||||
useSeoMeta({
|
||||
titleTemplate: `%s - Nuxt UI`,
|
||||
title: page.title,
|
||||
description: page.description,
|
||||
ogTitle: `${page.title} - Nuxt UI`,
|
||||
ogDescription: page.description,
|
||||
ogImage: joinURL(url, '/og-image.png')
|
||||
})
|
||||
|
||||
const { data: components } = await useAsyncData('ui-components', () => {
|
||||
return queryCollection('content')
|
||||
.where('path', 'LIKE', '/components/%')
|
||||
.where('extension', '=', 'md')
|
||||
.where('module', 'IS NULL')
|
||||
.select('path', 'title', 'description', 'category', 'module')
|
||||
.all()
|
||||
})
|
||||
|
||||
const { data: module } = await useFetch<{
|
||||
stats: {
|
||||
downloads: number
|
||||
stars: number
|
||||
}
|
||||
contributors: {
|
||||
username: string
|
||||
}[]
|
||||
}>('https://api.nuxt.com/modules/ui', {
|
||||
key: 'stats',
|
||||
transform: ({ stats, contributors }) => ({ stats, contributors })
|
||||
})
|
||||
|
||||
const { format } = Intl.NumberFormat('en', { notation: 'compact' })
|
||||
|
||||
const contributorsRef = ref(null)
|
||||
const isContributorsInView = ref(false)
|
||||
const isContributorsHovered = useElementHover(contributorsRef)
|
||||
|
||||
useIntersectionObserver(contributorsRef, ([entry]) => {
|
||||
isContributorsInView.value = entry?.isIntersecting || false
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UMain>
|
||||
<UPageHero
|
||||
orientation="horizontal"
|
||||
:ui="{
|
||||
container: 'pb-0 sm:pb-0 lg:py-0',
|
||||
title: 'lg:mt-16',
|
||||
links: 'lg:mb-16',
|
||||
description: 'text-balance'
|
||||
}"
|
||||
>
|
||||
<template #title>
|
||||
The Intuitive <br> <span class="text-(--ui-primary)">Vue UI Library</span>
|
||||
</template>
|
||||
<template #description>
|
||||
{{ page.hero.description }}
|
||||
</template>
|
||||
|
||||
<template #links>
|
||||
<UButton v-for="link of page.hero.links" :key="link.label" v-bind="link" size="xl" />
|
||||
<div class="w-full my-6">
|
||||
<USeparator class="w-1/2" type="dashed" />
|
||||
</div>
|
||||
<div class="flex flex-col gap-4">
|
||||
<Motion
|
||||
v-for="(feature, index) in page.hero.features"
|
||||
:key="feature.title"
|
||||
as-child
|
||||
:initial="{ opacity: 0, transform: 'translateX(-10px)' }"
|
||||
:in-view="{ opacity: 1, transform: 'translateX(0)' }"
|
||||
:transition="{ delay: 0.2 + 0.4 * index }"
|
||||
:in-view-options="{ once: true }"
|
||||
>
|
||||
<UPageFeature v-bind="feature" class="opacity-0" />
|
||||
</Motion>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<SkyBg />
|
||||
|
||||
<div class="h-[344px] lg:h-full lg:relative w-full lg:min-h-[calc(100vh-var(--ui-header-height)-1px)] overflow-hidden">
|
||||
<UPageMarquee
|
||||
pause-on-hover
|
||||
:overlay="false"
|
||||
:ui="{
|
||||
root: '[--gap:--spacing(4)] [--duration:40s] border-(--ui-border) absolute w-full left-0 border-y lg:border-x lg:border-y-0 lg:w-[calc(50%-6px)] 2xl:w-[320px] lg:flex-col',
|
||||
content: 'lg:w-auto lg:h-full lg:flex-col lg:animate-[marquee-vertical_var(--duration)_linear_infinite] lg:rtl:animate-[marquee-vertical-rtl_var(--duration)_linear_infinite] lg:h-[fit-content]'
|
||||
}"
|
||||
>
|
||||
<ULink
|
||||
v-for="component of components?.slice(0, 10)"
|
||||
:key="component.path"
|
||||
class="relative group/link aspect-video border-(--ui-border) w-[290px] xl:w-[330px] 2xl:w-[320px] 2xl:p-2 2xl:border-y"
|
||||
:to="component.path"
|
||||
>
|
||||
<UColorModeImage
|
||||
|
||||
:light="`${component.path.replace('/components/', '/components/light/')}.png`"
|
||||
:dark="`${component.path.replace('/components/', '/components/dark/')}.png`"
|
||||
class="hover:scale-105 lg:hover:scale-110 transition-transform aspect-video w-full border-x lg:border-x-0 lg:border-y border-(--ui-border) 2xl:border-y-0"
|
||||
/>
|
||||
<UBadge color="neutral" variant="outline" size="md" :label="component.title" class="hidden lg:block absolute mx-auto top-4 left-6 xl:left-4 group-hover/link:opacity-100 opacity-0 transition-all duration-300 pointer-events-none -translate-y-2 group-hover/link:translate-y-0" />
|
||||
</ULink>
|
||||
</UPageMarquee>
|
||||
|
||||
<UPageMarquee
|
||||
pause-on-hover
|
||||
reverse
|
||||
:overlay="false"
|
||||
:ui="{
|
||||
root: '[--gap:--spacing(4)] [--duration:40s] border-(--ui-border) absolute w-full mt-[180px] left-0 border-y lg:mt-auto lg:left-auto lg:border-y-0 lg:border-x lg:w-[calc(50%-6px)] 2xl:w-[320px] lg:right-0 lg:flex-col',
|
||||
content: 'lg:w-auto lg:h-full lg:flex-col lg:animate-[marquee-vertical_var(--duration)_linear_infinite] lg:rtl:animate-[marquee-vertical-rtl_var(--duration)_linear_infinite] lg:h-[fit-content] lg:[animation-direction:reverse]'
|
||||
}"
|
||||
>
|
||||
<ULink
|
||||
v-for="component of components?.slice(10, 20)"
|
||||
:key="component.path"
|
||||
class="relative group/link aspect-video border-(--ui-border) w-[290px] xl:w-[330px] 2xl:w-[320px] 2xl:p-2 2xl:border-y"
|
||||
:to="component.path"
|
||||
>
|
||||
<UColorModeImage
|
||||
:light="`${component.path.replace('/components/', '/components/light/')}.png`"
|
||||
:dark="`${component.path.replace('/components/', '/components/dark/')}.png`"
|
||||
class="hover:scale-105 lg:hover:scale-110 transition-transform aspect-video w-full border-x lg:border-x-0 lg:border-y border-(--ui-border) 2xl:border-y-0"
|
||||
/>
|
||||
<UBadge color="neutral" variant="outline" size="md" :label="component.title" class="hidden lg:block absolute mx-auto top-4 left-6 xl:left-4 group-hover/link:opacity-100 opacity-0 transition-all duration-300 pointer-events-none -translate-y-2 group-hover/link:translate-y-0" />
|
||||
</ULink>
|
||||
</UPageMarquee>
|
||||
</div>
|
||||
</UPageHero>
|
||||
|
||||
<USeparator />
|
||||
|
||||
<UPageSection :ui="{ container: 'lg:py-16' }">
|
||||
<ul class="grid grid-cols-1 gap-x-6 sm:grid-cols-2 lg:grid-cols-3 gap-y-6 lg:gap-x-8 lg:gap-y-8 xl:gap-y-10">
|
||||
<Motion
|
||||
v-for="(feature, index) in page?.features"
|
||||
:key="feature.title"
|
||||
as="li"
|
||||
:initial="{ opacity: 0, transform: 'translateY(10px)' }"
|
||||
:in-view="{ opacity: 1, transform: 'translateY(0)' }"
|
||||
:transition="{ delay: 0.1 * index }"
|
||||
:in-view-options="{ once: true }"
|
||||
class="flex items-start gap-x-3 relative group"
|
||||
>
|
||||
<NuxtLink v-if="feature.to" :to="feature.to" class="absolute inset-0 z-10" />
|
||||
|
||||
<div class="relative p-3">
|
||||
<svg class="absolute inset-0" viewBox="0 0 44 44" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<line x1="6.5" x2="6.5" y2="44" stroke="var(--ui-border)" />
|
||||
<line x1="38.5" x2="38.5" y2="44" stroke="var(--ui-border)" />
|
||||
<line y1="5.5" x2="44" y2="5.5" stroke="var(--ui-border)" />
|
||||
<line y1="37.5" x2="44" y2="37.5" stroke="var(--ui-border)" />
|
||||
<circle cx="6.53613" cy="5.45508" r="1.5" fill="var(--ui-border-accented)" />
|
||||
<circle cx="38.5957" cy="5.45508" 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)" />
|
||||
</svg>
|
||||
<UIcon :name="feature.icon" class="size-5 flex-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" />
|
||||
</h2>
|
||||
<p class="text-sm text-(--ui-text-muted)">
|
||||
{{ feature.description }}
|
||||
</p>
|
||||
</div>
|
||||
</Motion>
|
||||
</ul>
|
||||
</UPageSection>
|
||||
|
||||
<USeparator />
|
||||
|
||||
<UPageSection
|
||||
:title="page.design_system.title"
|
||||
:description="page.design_system.description"
|
||||
:features="page.design_system.features"
|
||||
:links="page.design_system.links"
|
||||
orientation="horizontal"
|
||||
>
|
||||
<MDC :value="page.design_system.code" />
|
||||
</UPageSection>
|
||||
|
||||
<USeparator />
|
||||
|
||||
<UPageSection
|
||||
:title="page.component_customization.title"
|
||||
:features="page.component_customization.features"
|
||||
:links="page.component_customization.links"
|
||||
orientation="horizontal"
|
||||
>
|
||||
<template #description>
|
||||
<MDC :value="page.component_customization.description" />
|
||||
</template>
|
||||
|
||||
<MDC :value="page.component_customization.code" />
|
||||
</UPageSection>
|
||||
|
||||
<USeparator />
|
||||
|
||||
<UPageSection
|
||||
:title="page.community.title"
|
||||
:description="page.community.description"
|
||||
:links="page.community.links"
|
||||
orientation="horizontal"
|
||||
:ui="{ features: 'flex items-center gap-4 lg:gap-8' }"
|
||||
class="border-b border-(--ui-border)"
|
||||
>
|
||||
<template #features>
|
||||
<NuxtLink to="https://npm.chart.dev/@nuxt/ui" target="_blank" class="min-w-0">
|
||||
<p class="text-4xl font-semibold text-(--ui-text-highlighted) truncate">
|
||||
{{ format(module?.stats?.downloads ?? 0) }}+
|
||||
</p>
|
||||
<p class="text-(--ui-text-muted) text-sm truncate">monthly downloads</p>
|
||||
</NuxtLink>
|
||||
|
||||
<NuxtLink to="https://github.com/nuxt/ui" target="_blank" class="min-w-0">
|
||||
<p class="text-4xl font-semibold text-(--ui-text-highlighted) truncate">
|
||||
{{ format(module?.stats?.stars ?? 0) }}+
|
||||
</p>
|
||||
<p class="text-(--ui-text-muted) text-sm truncate">GitHub stars</p>
|
||||
</NuxtLink>
|
||||
|
||||
<NuxtLink to="https://github.com/nuxt/ui/graphs/contributors" target="_blank" class="min-w-0">
|
||||
<p class="text-4xl font-semibold text-(--ui-text-highlighted) truncate">
|
||||
175+
|
||||
</p>
|
||||
<p class="text-(--ui-text-muted) text-sm truncate">Contributors</p>
|
||||
</NuxtLink>
|
||||
</template>
|
||||
|
||||
<div ref="contributorsRef" class="p-4 sm:px-6 md:px-8 lg:px-12 xl:px-14 overflow-hidden flex relative">
|
||||
<LazyHomeContributors :contributors="module?.contributors" :paused="!isContributorsInView || isContributorsHovered" />
|
||||
</div>
|
||||
</UPageSection>
|
||||
|
||||
<UPageSection :ui="{ container: 'relative !pb-0 overflow-hidden' }">
|
||||
<template #title>
|
||||
Build faster with Nuxt UI <span class="text-(--ui-primary)">Pro</span>.
|
||||
</template>
|
||||
<template #description>
|
||||
A collection of premium Vue components, composables and utils built on top of Nuxt UI. <br> Focused on structure and layout, these <span class="text-(--ui-text)">responsive components</span> are designed to be the perfect <span class="text-(--ui-text)">building blocks for your next idea</span>.
|
||||
</template>
|
||||
<template #links>
|
||||
<UButton to="/pro" size="lg">
|
||||
Discover Nuxt UI Pro
|
||||
</UButton>
|
||||
<UButton to="/pro/templates" size="lg" variant="outline" trailing-icon="i-lucide-arrow-right" color="neutral">
|
||||
Explore templates
|
||||
</UButton>
|
||||
</template>
|
||||
|
||||
<StarsBg />
|
||||
|
||||
<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="relative h-[400px] border border-(--ui-border) bg-(--ui-bg-muted) overflow-hidden border-x-0 -mx-4 sm:-mx-6 lg:mx-0 lg:border-x w-screen lg:w-full">
|
||||
<UPageMarquee reverse orientation="vertical" :overlay="false" :ui="{ root: '[--duration:40s] absolute w-[460px] -left-[100px] -top-[300px] h-[940px] transform-3d rotate-x-55 rotate-y-0 rotate-z-30' }">
|
||||
<img
|
||||
v-for="i in 4"
|
||||
:key="i"
|
||||
:src="`/pro/blocks/image${i}.png`"
|
||||
width="460"
|
||||
height="258"
|
||||
loading="lazy"
|
||||
:alt="`Nuxt UI Pro Screenshot ${i}`"
|
||||
class="aspect-video border border-(--ui-border) rounded-[calc(var(--ui-radius)*2)] bg-white"
|
||||
>
|
||||
</UPageMarquee>
|
||||
<UPageMarquee orientation="vertical" :overlay="false" :ui="{ root: '[--duration:40s] absolute w-[460px] -top-[400px] left-[480px] h-[1160px] transform-3d rotate-x-55 rotate-y-0 rotate-z-30' }">
|
||||
<img
|
||||
v-for="i in [5, 6, 7, 8]"
|
||||
:key="i"
|
||||
:src="`/pro/blocks/image${i}.png`"
|
||||
width="460"
|
||||
height="258"
|
||||
loading="lazy"
|
||||
:alt="`Nuxt UI Pro Screenshot ${i}`"
|
||||
class="aspect-video border border-(--ui-border) rounded-[calc(var(--ui-radius)*2)] bg-white"
|
||||
>
|
||||
</UPageMarquee>
|
||||
<UPageMarquee reverse orientation="vertical" :overlay="false" :ui="{ root: 'hidden md:flex [--duration:40s] absolute w-[460px] -top-[300px] left-[1020px] h-[1060px] transform-3d rotate-x-55 rotate-y-0 rotate-z-30' }">
|
||||
<img
|
||||
v-for="i in [9, 10, 11, 12]"
|
||||
:key="i"
|
||||
:src="`/pro/blocks/image${i}.png`"
|
||||
width="460"
|
||||
height="258"
|
||||
:alt="`Nuxt UI Pro Screenshot ${i}`"
|
||||
loading="lazy"
|
||||
class="aspect-video border border-(--ui-border) rounded-[calc(var(--ui-radius)*2)] bg-white"
|
||||
>
|
||||
</UPageMarquee>
|
||||
</div>
|
||||
</UPageSection>
|
||||
</UMain>
|
||||
</template>
|
||||
@@ -30,7 +30,7 @@ templates:
|
||||
- title: Resizable multi-column layout
|
||||
icon: i-lucide-columns-3
|
||||
links:
|
||||
- label: Live Preview
|
||||
- label: Preview
|
||||
to: https://dashboard-template.nuxt.dev
|
||||
target: _blank
|
||||
leadingIcon: i-logos-nuxt-icon
|
||||
@@ -42,7 +42,7 @@ templates:
|
||||
icon: i-simple-icons-github
|
||||
color: neutral
|
||||
variant: outline
|
||||
- label: Live Preview
|
||||
- label: Preview
|
||||
to: https://vue-dashboard-template.nuxt.dev
|
||||
target: _blank
|
||||
leadingIcon: i-logos-vue
|
||||
@@ -68,7 +68,7 @@ templates:
|
||||
- title: Authentication pages (login, register)
|
||||
icon: i-lucide-user-round-check
|
||||
links:
|
||||
- label: Live Preview
|
||||
- label: Preview
|
||||
to: https://saas-template.nuxt.dev
|
||||
target: _blank
|
||||
leadingIcon: i-logos-nuxt-icon
|
||||
@@ -94,7 +94,7 @@ templates:
|
||||
- title: Write content in YAML
|
||||
icon: i-simple-icons-yaml
|
||||
links:
|
||||
- label: Live Preview
|
||||
- label: Preview
|
||||
to: https://landing-template.nuxt.dev
|
||||
target: _blank
|
||||
leadingIcon: i-logos-nuxt-icon
|
||||
@@ -120,7 +120,7 @@ templates:
|
||||
- title: Full-text search out of the box
|
||||
icon: i-lucide-search
|
||||
links:
|
||||
- label: Live Preview
|
||||
- label: Preview
|
||||
to: https://docs-template.nuxt.dev
|
||||
target: _blank
|
||||
leadingIcon: i-logos-nuxt-icon
|
||||
@@ -144,7 +144,7 @@ templates:
|
||||
- title: Nuxt 4 Compatibility Enabled
|
||||
icon: i-simple-icons-nuxtdotjs
|
||||
links:
|
||||
- label: Live Preview
|
||||
- label: Preview
|
||||
to: https://ui-pro-starter.nuxt.dev
|
||||
target: _blank
|
||||
leadingIcon: i-logos-nuxt-icon
|
||||
@@ -156,7 +156,7 @@ templates:
|
||||
variant: outline
|
||||
icon: i-simple-icons-github
|
||||
color: neutral
|
||||
- label: Live Preview
|
||||
- label: Preview
|
||||
to: https://ui-pro-starter-vue.nuxt.dev
|
||||
target: _blank
|
||||
leadingIcon: i-logos-vue
|
||||
|
||||
@@ -70,14 +70,12 @@ onMounted(() => {
|
||||
|
||||
<template>
|
||||
<UMain>
|
||||
<UPageHero headline="License Activation" :title="title" :description="description" :ui="{ container: 'relative' }">
|
||||
<template #top>
|
||||
<StarsBg />
|
||||
</template>
|
||||
<UPageHero headline="License Activation" :title="title" :description="description" :ui="{ container: 'relative overflow-hidden', wrapper: 'lg:px-12', description: 'text-pretty' }">
|
||||
<StarsBg />
|
||||
<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="lg:border-y border-(--ui-border)">
|
||||
<UCard class="lg:w-1/2 m-auto lg:rounded-none overflow-hidden" variant="outline" :ui="{ footer: 'bg-(--ui-bg-muted)' }">
|
||||
<div class="px-4 py-10 lg:border border-(--ui-border) bg-(--ui-bg)">
|
||||
<div class="max-w-xl mx-auto">
|
||||
<UForm
|
||||
:schema="schema"
|
||||
:validate-on="['blur']"
|
||||
@@ -107,12 +105,13 @@ onMounted(() => {
|
||||
</UAlert>
|
||||
<UAlert v-else-if="errorMessage" color="error" variant="subtle" :title="errorMessage" />
|
||||
</UForm>
|
||||
<template #footer>
|
||||
<p class="text-sm text-center text-neutral-500 dark:text-neutral-400">
|
||||
If you purchased a license with multiple seats, activate the license key for each member of your team.
|
||||
</p>
|
||||
</template>
|
||||
</UCard>
|
||||
|
||||
<ProseHr />
|
||||
|
||||
<ProseNote>
|
||||
If you purchased a license with multiple seats, activate the license key for each member of your team.
|
||||
</ProseNote>
|
||||
</div>
|
||||
</div>
|
||||
</UPageHero>
|
||||
</UMain>
|
||||
|
||||
@@ -26,15 +26,14 @@ useSeoMeta({
|
||||
}"
|
||||
>
|
||||
<template #title>
|
||||
<MDC :value="page.hero.title" unwrap="p" />
|
||||
<MDC :value="page.hero.title" tag="span" unwrap="p" />
|
||||
</template>
|
||||
<template #description>
|
||||
<MDC :value="page.hero.description" unwrap="p" />
|
||||
</template>
|
||||
<template #top>
|
||||
<StarsBg />
|
||||
<MDC :value="page.hero.description" tag="span" unwrap="p" />
|
||||
</template>
|
||||
|
||||
<StarsBg />
|
||||
|
||||
<Motion as-child :initial="{ height: 0 }" :animate="{ height: 'auto' }" :transition="{ delay: 0.2, duration: 1 }">
|
||||
<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" />
|
||||
</Motion>
|
||||
@@ -84,7 +83,7 @@ useSeoMeta({
|
||||
>
|
||||
<template #description>
|
||||
<Motion :initial="{ opacity: 0, transform: 'translateY(10px)' }" :in-view="{ opacity: 1, transform: 'translateY(0)' }" :in-view-options="{ once: true }" :transition="{ delay: 0.2 }">
|
||||
<MDC :value="page.testimonial.quote" unwrap="p" class="before:content-[open-quote] after:content-[close-quote] " />
|
||||
<MDC :value="page.testimonial.quote" tag="span" unwrap="p" class="before:content-[open-quote] after:content-[close-quote] " />
|
||||
</Motion>
|
||||
</template>
|
||||
<Motion :initial="{ opacity: 0, transform: 'translateY(10px)' }" :in-view="{ opacity: 1, transform: 'translateY(0)' }" :in-view-options="{ once: true }" :transition="{ delay: 0.3 }">
|
||||
@@ -120,10 +119,10 @@ useSeoMeta({
|
||||
class="rounded-none border-t border-(--ui-border) bg-gradient-to-b from-(--ui-bg-muted) to-(--ui-bg)"
|
||||
>
|
||||
<template #title>
|
||||
<MDC :value="page.mainSection.title" unwrap="p" />
|
||||
<MDC :value="page.mainSection.title" tag="span" unwrap="p" />
|
||||
</template>
|
||||
<template #description>
|
||||
<MDC :value="page.mainSection.description" unwrap="p" />
|
||||
<MDC :value="page.mainSection.description" tag="span" unwrap="p" />
|
||||
</template>
|
||||
</UPageCTA>
|
||||
<UPageSection
|
||||
@@ -198,6 +197,7 @@ useSeoMeta({
|
||||
orientation="horizontal"
|
||||
>
|
||||
<StarsBg />
|
||||
|
||||
<video
|
||||
class="rounded-[var(--ui-radius)] z-10"
|
||||
preload="none"
|
||||
|
||||
@@ -26,9 +26,8 @@ useSeoMeta({
|
||||
<template #title>
|
||||
<MDC :value="page.pricing.title" unwrap="p" />
|
||||
</template>
|
||||
<template #top>
|
||||
<StarsBg />
|
||||
</template>
|
||||
|
||||
<StarsBg />
|
||||
<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="flex flex-col bg-(--ui-bg) gap-8 lg:gap-0">
|
||||
<UPricingPlan
|
||||
|
||||
@@ -16,59 +16,60 @@ useSeoMeta({
|
||||
|
||||
<!-- eslint-disable vue/no-v-html -->
|
||||
<template>
|
||||
<UPageHero :links="page.links" :ui="{ container: 'relative' }">
|
||||
<template #top>
|
||||
<div class="relative">
|
||||
<UPageHero :links="page.links" :ui="{ container: 'relative' }">
|
||||
<StarsBg />
|
||||
</template>
|
||||
<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" />
|
||||
|
||||
<template #title>
|
||||
<MDC :value="page.hero.title" unwrap="p" />
|
||||
</template>
|
||||
<template #title>
|
||||
<MDC :value="page.hero.title" unwrap="p" />
|
||||
</template>
|
||||
|
||||
<template #description>
|
||||
<MDC :value="page.hero.description" unwrap="p" />
|
||||
</template>
|
||||
</UPageHero>
|
||||
<template #description>
|
||||
<MDC :value="page.hero.description" unwrap="p" />
|
||||
</template>
|
||||
</UPageHero>
|
||||
|
||||
<UPageSection
|
||||
v-for="(template, index) in page.templates"
|
||||
:key="index"
|
||||
:title="template.title"
|
||||
:links="template.links"
|
||||
:features="template.features"
|
||||
orientation="horizontal"
|
||||
class="lg:border-t border-(--ui-border)"
|
||||
:ui="{
|
||||
title: 'lg:text-4xl',
|
||||
wrapper: 'lg:py-16 lg:border-r border-(--ui-border) order-last lg:pr-16',
|
||||
container: 'lg:py-0'
|
||||
}"
|
||||
>
|
||||
<template #description>
|
||||
<MDC :value="template.description" unwrap="p" />
|
||||
</template>
|
||||
<UPageSection
|
||||
v-for="(template, index) in page.templates"
|
||||
:key="index"
|
||||
:title="template.title"
|
||||
:links="template.links"
|
||||
:features="template.features"
|
||||
orientation="horizontal"
|
||||
class="lg:border-t border-(--ui-border)"
|
||||
:ui="{
|
||||
title: 'lg:text-4xl',
|
||||
wrapper: 'lg:py-16 lg:border-r border-(--ui-border) order-last lg:pr-16',
|
||||
container: 'lg:py-0',
|
||||
links: 'gap-x-3'
|
||||
}"
|
||||
>
|
||||
<template #description>
|
||||
<MDC :value="template.description" unwrap="p" />
|
||||
</template>
|
||||
|
||||
<div class="lg:border-x border-(--ui-border) h-full flex items-center lg:bg-(--ui-bg-muted)/20">
|
||||
<Motion class="flex-1" :initial="{ opacity: 0, transform: 'translateY(10px)' }" :in-view="{ opacity: 1, transform: 'translateY(0px)' }" :in-view-options="{ once: true }" :transition="{ duration: 0.5, delay: 0.2 }">
|
||||
<UColorModeImage
|
||||
v-if="template.thumbnail"
|
||||
v-bind="template.thumbnail"
|
||||
class="w-full h-auto border lg:border-y lg:border-x-0 border-(--ui-border) rounded-(--ui-radius) lg:rounded-none"
|
||||
width="656"
|
||||
height="369"
|
||||
loading="lazy"
|
||||
/>
|
||||
<UCarousel
|
||||
v-else-if="template.images"
|
||||
v-slot="{ item }"
|
||||
:items="(template.images as any[])"
|
||||
dots
|
||||
>
|
||||
<NuxtImg v-bind="item" class="w-full h-full object-cover" width="576" height="360" />
|
||||
</UCarousel>
|
||||
<Placeholder v-else class="w-full h-full aspect-video" />
|
||||
</Motion>
|
||||
</div>
|
||||
</UPageSection>
|
||||
<div class="lg:border-x border-(--ui-border) h-full flex items-center lg:bg-(--ui-bg-muted)/20">
|
||||
<Motion class="flex-1" :initial="{ opacity: 0, transform: 'translateY(10px)' }" :in-view="{ opacity: 1, transform: 'translateY(0px)' }" :in-view-options="{ once: true }" :transition="{ duration: 0.5, delay: 0.2 }">
|
||||
<UColorModeImage
|
||||
v-if="template.thumbnail"
|
||||
v-bind="template.thumbnail"
|
||||
class="w-full h-auto border lg:border-y lg:border-x-0 border-(--ui-border) rounded-(--ui-radius) lg:rounded-none"
|
||||
width="656"
|
||||
height="369"
|
||||
loading="lazy"
|
||||
/>
|
||||
<UCarousel
|
||||
v-else-if="template.images"
|
||||
v-slot="{ item }"
|
||||
:items="(template.images as any[])"
|
||||
dots
|
||||
>
|
||||
<NuxtImg v-bind="item" class="w-full h-full object-cover" width="576" height="360" />
|
||||
</UCarousel>
|
||||
<Placeholder v-else class="w-full h-full aspect-video" />
|
||||
</Motion>
|
||||
</div>
|
||||
</UPageSection>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -85,7 +85,6 @@ export default defineNuxtConfig({
|
||||
},
|
||||
|
||||
routeRules: {
|
||||
'/': { redirect: '/getting-started', prerender: false },
|
||||
'/getting-started/installation': { redirect: '/getting-started/installation/nuxt', prerender: false },
|
||||
'/getting-started/icons': { redirect: '/getting-started/icons/nuxt', prerender: false },
|
||||
'/getting-started/color-mode': { redirect: '/getting-started/color-mode/nuxt', prerender: false },
|
||||
|
||||
@@ -29,6 +29,7 @@ export interface AvatarProps {
|
||||
*/
|
||||
size?: AvatarVariants['size']
|
||||
class?: any
|
||||
style?: any
|
||||
ui?: Partial<typeof avatar.slots>
|
||||
}
|
||||
|
||||
@@ -83,7 +84,7 @@ function onError() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Primitive :as="as" :class="ui.root({ class: [props.class, props.ui?.root] })">
|
||||
<Primitive :as="as" :class="ui.root({ class: [props.class, props.ui?.root] })" :style="props.style">
|
||||
<component
|
||||
:is="ImageComponent"
|
||||
v-if="src && !error"
|
||||
|
||||
Reference in New Issue
Block a user