mirror of
https://github.com/ArthurDanjou/ui.git
synced 2026-01-14 20:19:34 +01:00
Merge branch 'v3' into chore/content-3.3
This commit is contained in:
4
.github/ISSUE_TEMPLATE/bug-report-v3.yml
vendored
4
.github/ISSUE_TEMPLATE/bug-report-v3.yml
vendored
@@ -5,7 +5,7 @@ body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Before reporting a bug, please make sure that you have read through our [v3 documentation](https://ui3.nuxt.dev/) and existing [issues](https://github.com/nuxt/ui/issues?q=is%3Aissue%20is%3Aopen%20sort%3Aupdated-desc%20label%3Av3).
|
||||
Before reporting a bug, please make sure that you have read through our [v3 documentation](https://ui.nuxt.com/) and existing [issues](https://github.com/nuxt/ui/issues?q=is%3Aissue%20is%3Aopen%20sort%3Aupdated-desc%20label%3Av3).
|
||||
- type: textarea
|
||||
id: env
|
||||
attributes:
|
||||
@@ -37,7 +37,7 @@ body:
|
||||
id: version
|
||||
attributes:
|
||||
label: Version
|
||||
placeholder: v3.0.0-alpha.x
|
||||
placeholder: v3.0.0
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
2
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
@@ -5,7 +5,7 @@ body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Before reporting a bug, please make sure that you have read through our [documentation](https://ui.nuxt.com) and existing [issues](https://github.com/nuxt/ui/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc).
|
||||
Before reporting a bug, please make sure that you have read through our [documentation](https://ui2.nuxt.com) and existing [issues](https://github.com/nuxt/ui/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc).
|
||||
- type: textarea
|
||||
id: env
|
||||
attributes:
|
||||
|
||||
@@ -5,7 +5,7 @@ body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Before requesting a feature, please make sure that you have read through our [v3 documentation](https://ui3.nuxt.dev/) and existing [issues](https://github.com/nuxt/ui/issues?q=is%3Aissue%20is%3Aopen%20sort%3Aupdated-desc%20label%3Av3).
|
||||
Before requesting a feature, please make sure that you have read through our [v3 documentation](https://ui.nuxt.com/) and existing [issues](https://github.com/nuxt/ui/issues?q=is%3Aissue%20is%3Aopen%20sort%3Aupdated-desc%20label%3Av3).
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/feature-request.yml
vendored
2
.github/ISSUE_TEMPLATE/feature-request.yml
vendored
@@ -5,7 +5,7 @@ body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Before requesting a feature, please make sure that you have read through our [documentation](https://ui.nuxt.com) and existing [issues](https://github.com/nuxt/ui/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc).
|
||||
Before requesting a feature, please make sure that you have read through our [documentation](https://ui2.nuxt.com) and existing [issues](https://github.com/nuxt/ui/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc).
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/question-v3.yml
vendored
2
.github/ISSUE_TEMPLATE/question-v3.yml
vendored
@@ -5,7 +5,7 @@ body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Before asking a question, please make sure that you have read through our [v3 documentation](https://ui3.nuxt.dev/) and existing [issues](https://github.com/nuxt/ui/issues?q=is%3Aissue%20is%3Aopen%20sort%3Aupdated-desc%20label%3Av3).
|
||||
Before asking a question, please make sure that you have read through our [v3 documentation](https://ui.nuxt.com/) and existing [issues](https://github.com/nuxt/ui/issues?q=is%3Aissue%20is%3Aopen%20sort%3Aupdated-desc%20label%3Av3).
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/question.yml
vendored
2
.github/ISSUE_TEMPLATE/question.yml
vendored
@@ -5,7 +5,7 @@ body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Before asking a question, please make sure that you have read through our [documentation](https://ui.nuxt.com) and existing [issues](https://github.com/nuxt/ui/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc).
|
||||
Before asking a question, please make sure that you have read through our [documentation](https://ui2.nuxt.com) and existing [issues](https://github.com/nuxt/ui/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc).
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
|
||||
86
.github/workflows/integration.yml
vendored
Normal file
86
.github/workflows/integration.yml
vendored
Normal file
@@ -0,0 +1,86 @@
|
||||
name: integration
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ["module"]
|
||||
types:
|
||||
- completed
|
||||
|
||||
jobs:
|
||||
nuxt:
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest] # macos-latest, windows-latest
|
||||
node: [22]
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: benjamincanac/app-ui3
|
||||
|
||||
- name: Set short SHA
|
||||
run: echo "COMMIT=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
|
||||
- name: Install node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
cache: pnpm
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install https://pkg.pr.new/@nuxt/ui@${{ env.COMMIT }}
|
||||
|
||||
- name: Typecheck
|
||||
run: pnpm run typecheck
|
||||
|
||||
- name: Build
|
||||
run: pnpm run build
|
||||
|
||||
vue:
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest] # macos-latest, windows-latest
|
||||
node: [22]
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: benjamincanac/app-ui3-vue
|
||||
|
||||
- name: Set short SHA
|
||||
run: echo "COMMIT=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
|
||||
- name: Install node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
cache: pnpm
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install https://pkg.pr.new/@nuxt/ui@${{ env.COMMIT }}
|
||||
|
||||
# - name: Typecheck
|
||||
# run: pnpm run typecheck
|
||||
|
||||
- name: Build
|
||||
run: pnpm run build
|
||||
31
CHANGELOG.md
31
CHANGELOG.md
@@ -1,5 +1,36 @@
|
||||
# Changelog
|
||||
|
||||
## [3.0.0](https://github.com/nuxt/ui/compare/v3.0.0-beta.4...v3.0.0) (2025-03-12)
|
||||
|
||||
## [3.0.0-beta.4](https://github.com/nuxt/ui/compare/v3.0.0-beta.3...v3.0.0-beta.4) (2025-03-12)
|
||||
|
||||
### Features
|
||||
|
||||
* **Form:** global errors ([#3482](https://github.com/nuxt/ui/issues/3482)) ([6e03d9c](https://github.com/nuxt/ui/commit/6e03d9c6efc8f4cfc306813e733d7d3e03706323))
|
||||
* **Input/Textarea:** allow `null` value in model ([#3415](https://github.com/nuxt/ui/issues/3415)) ([cfe9b2e](https://github.com/nuxt/ui/commit/cfe9b2ecf34827bc11a5281a069988ab96030047))
|
||||
* **useLocale:** handle generic messages ([#3100](https://github.com/nuxt/ui/issues/3100)) ([a9c8eb3](https://github.com/nuxt/ui/commit/a9c8eb3f60a10d1a71632991c9db594716b0fba1))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **Button:** missing import ([21dbf01](https://github.com/nuxt/ui/commit/21dbf01888a161a9d8ac6eb0d957c1342f6cc30d)), closes [nuxt/ui#3417](https://github.com/nuxt/ui/issues/3417)
|
||||
* **Form:** input blur validation on submit ([#3504](https://github.com/nuxt/ui/issues/3504)) ([97c8098](https://github.com/nuxt/ui/commit/97c8098d4a35c392719ae179d36aa008d6f8f78a))
|
||||
* **vue:** prevent calling `useHead` in colors ([5ecd227](https://github.com/nuxt/ui/commit/5ecd2271ca86087cb805548397d75c38763ad412))
|
||||
|
||||
## [3.0.0-beta.3](https://github.com/nuxt/ui/compare/v3.0.0-beta.2...v3.0.0-beta.3) (2025-03-07)
|
||||
|
||||
### Features
|
||||
|
||||
* **Button:** handle `active` state ([bd2d484](https://github.com/nuxt/ui/commit/bd2d4848d246a3d5930f8059913f5a1a0abe29fd)), closes [#3417](https://github.com/nuxt/ui/issues/3417)
|
||||
* **Table:** add `loading` slot ([99e531d](https://github.com/nuxt/ui/commit/99e531d8dfb7954322b7ab7feda3d8814c6d8d02)), closes [#3444](https://github.com/nuxt/ui/issues/3444)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **InputMenu/SelectMenu:** proxy `required` in root props ([60b7e2d](https://github.com/nuxt/ui/commit/60b7e2d69e80afa7e221855dcec46479d0ca5c6c))
|
||||
* **InputMenu:** wrong `required` in multiple mode ([01fa230](https://github.com/nuxt/ui/commit/01fa230eae4b6623c5fd71cc218d114d9f6f0f25)), closes [#2741](https://github.com/nuxt/ui/issues/2741)
|
||||
* **Pagination:** add missing slots ([a47c5ff](https://github.com/nuxt/ui/commit/a47c5ff46616eafee3158cb9801183965f5f9874)), closes [#3441](https://github.com/nuxt/ui/issues/3441)
|
||||
* **Pagination:** wrong next link ([e823022](https://github.com/nuxt/ui/commit/e823022b19bb172d2e5fabb9144b4a4286a25a5f)), closes [#3008](https://github.com/nuxt/ui/issues/3008)
|
||||
* **templates:** prevent overriding existing colors ([ccbd89c](https://github.com/nuxt/ui/commit/ccbd89c908fe8af54c7d723dd12da5b7f3906c8f)), closes [#3426](https://github.com/nuxt/ui/issues/3426)
|
||||
|
||||
## [3.0.0-beta.2](https://github.com/nuxt/ui/compare/v3.0.0-beta.1...v3.0.0-beta.2) (2025-02-28)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
20
README.md
20
README.md
@@ -11,31 +11,31 @@
|
||||
[![License][license-src]][license-href]
|
||||
[![Nuxt][nuxt-src]][nuxt-href]
|
||||
|
||||
We're thrilled to introduce Nuxt UI v3, a significant upgrade to our UI library that delivers extensive improvements and robust new capabilities. This major update harnesses the combined strengths of [Reka UI](https://reka-ui.com/), [Tailwind CSS v4](https://tailwindcss.com/), and [Tailwind Variants](https://www.tailwind-variants.org/) to offer developers an unparalleled set of tools for creating sophisticated, accessible, and highly performant user interfaces.
|
||||
Nuxt UI harnesses the combined strengths of [Reka UI](https://reka-ui.com/), [Tailwind CSS](https://tailwindcss.com/), and [Tailwind Variants](https://www.tailwind-variants.org/) to offer developers an unparalleled set of tools for creating sophisticated, accessible, and highly performant user interfaces.
|
||||
|
||||
> [!NOTE]
|
||||
> You are on the `v3` development branch, check out the [dev branch](https://github.com/nuxt/ui/tree/dev) for Nuxt UI v2.
|
||||
> You are on the `v3` development branch, check out the [v2 branch](https://github.com/nuxt/ui/tree/v2) for Nuxt UI v2.
|
||||
|
||||
## Documentation
|
||||
|
||||
Visit https://ui3.nuxt.dev to explore the documentation.
|
||||
Visit https://ui.nuxt.com to explore the documentation.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash [pnpm]
|
||||
pnpm add @nuxt/ui@next
|
||||
pnpm add @nuxt/ui
|
||||
```
|
||||
|
||||
```bash [yarn]
|
||||
yarn add @nuxt/ui@next
|
||||
yarn add @nuxt/ui
|
||||
```
|
||||
|
||||
```bash [npm]
|
||||
npm install @nuxt/ui@next
|
||||
npm install @nuxt/ui
|
||||
```
|
||||
|
||||
```bash [bun]
|
||||
bun add @nuxt/ui@next
|
||||
bun add @nuxt/ui
|
||||
```
|
||||
|
||||
### Nuxt
|
||||
@@ -55,7 +55,7 @@ export default defineNuxtConfig({
|
||||
@import "@nuxt/ui";
|
||||
```
|
||||
|
||||
Learn more in the [installation guide](https://ui3.nuxt.dev/getting-started/installation/nuxt).
|
||||
Learn more in the [installation guide](https://ui.nuxt.com/getting-started/installation/nuxt).
|
||||
|
||||
### Vue
|
||||
|
||||
@@ -102,7 +102,7 @@ app.mount('#app')
|
||||
@import "@nuxt/ui";
|
||||
```
|
||||
|
||||
Learn more in the [installation guide](https://ui3.nuxt.dev/getting-started/installation/vue).
|
||||
Learn more in the [installation guide](https://ui.nuxt.com/getting-started/installation/vue).
|
||||
|
||||
## Credits
|
||||
|
||||
@@ -119,7 +119,7 @@ Learn more in the [installation guide](https://ui3.nuxt.dev/getting-started/inst
|
||||
Licensed under the [MIT license](https://github.com/nuxt/ui/blob/v3/LICENSE.md).
|
||||
|
||||
<!-- Badges -->
|
||||
[npm-version-src]: https://img.shields.io/npm/v/@nuxt/ui/next.svg?style=flat&colorA=18181B&colorB=28CF8D
|
||||
[npm-version-src]: https://img.shields.io/npm/v/@nuxt/ui/latest.svg?style=flat&colorA=18181B&colorB=28CF8D
|
||||
[npm-version-href]: https://npmjs.com/package/@nuxt/ui
|
||||
|
||||
[npm-downloads-src]: https://img.shields.io/npm/dm/@nuxt/ui.svg?style=flat&colorA=18181B&colorB=28CF8D
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
// import { withoutTrailingSlash } from 'ufo'
|
||||
import { withoutTrailingSlash } from 'ufo'
|
||||
import colors from 'tailwindcss/colors'
|
||||
// import { debounce } from 'perfect-debounce'
|
||||
|
||||
const route = useRoute()
|
||||
const appConfig = useAppConfig()
|
||||
@@ -12,16 +11,6 @@ const { data: files } = useLazyAsyncData('search', () => queryCollectionSearchSe
|
||||
server: false
|
||||
})
|
||||
|
||||
const searchTerm = ref('')
|
||||
|
||||
// watch(searchTerm, debounce((query: string) => {
|
||||
// if (!query) {
|
||||
// return
|
||||
// }
|
||||
|
||||
// useTrackEvent('Search', { props: { query: `${query} - ${searchTerm.value?.commandPaletteRef.results.length} results` } })
|
||||
// }, 500))
|
||||
|
||||
const links = useLinks()
|
||||
const color = computed(() => colorMode.value === 'dark' ? (colors as any)[appConfig.ui.colors.neutral][900] : 'white')
|
||||
const radius = computed(() => `:root { --ui-radius: ${appConfig.theme.radius}rem; }`)
|
||||
@@ -33,8 +22,8 @@ useHead({
|
||||
{ key: 'theme-color', name: 'theme-color', content: color }
|
||||
],
|
||||
link: [
|
||||
{ rel: 'icon', type: 'image/svg+xml', href: '/icon.svg' }
|
||||
// { rel: 'canonical', href: `https://ui.nuxt.com${withoutTrailingSlash(route.path)}` }
|
||||
{ rel: 'icon', type: 'image/svg+xml', href: '/icon.svg' },
|
||||
{ rel: 'canonical', href: `https://ui.nuxt.com${withoutTrailingSlash(route.path)}` }
|
||||
],
|
||||
style: [
|
||||
{ innerHTML: radius, id: 'nuxt-ui-radius', tagPriority: -2 },
|
||||
@@ -61,7 +50,7 @@ provide('navigation', mappedNavigation)
|
||||
<NuxtLoadingIndicator color="var(--ui-primary)" :height="2" />
|
||||
|
||||
<template v-if="!route.path.startsWith('/examples')">
|
||||
<!-- <Banner /> -->
|
||||
<Banner />
|
||||
|
||||
<Header :links="links" />
|
||||
</template>
|
||||
@@ -75,7 +64,6 @@ provide('navigation', mappedNavigation)
|
||||
|
||||
<ClientOnly>
|
||||
<LazyUContentSearch
|
||||
v-model:search-term="searchTerm"
|
||||
:files="files"
|
||||
:groups="[{
|
||||
id: 'framework',
|
||||
@@ -95,5 +83,5 @@ provide('navigation', mappedNavigation)
|
||||
</template>
|
||||
|
||||
<style>
|
||||
/* Safelist (do not remove): [&>div]:*:my-0 [&>div]:*:w-full h-64 !px-0 !py-0 !pt-0 !pb-0 !p-0 !justify-start !min-h-96 h-136 */
|
||||
/* Safelist (do not remove): [&>div]:*:my-0 [&>div]:*:w-full h-64 !px-0 !py-0 !pt-0 !pb-0 !p-0 !justify-start !justify-end !min-h-96 h-136 */
|
||||
</style>
|
||||
|
||||
50
docs/app/components/AdsCarbon.vue
Normal file
50
docs/app/components/AdsCarbon.vue
Normal file
@@ -0,0 +1,50 @@
|
||||
<script setup lang="ts">
|
||||
const el = ref<HTMLDivElement | null>(null)
|
||||
|
||||
onMounted(() => {
|
||||
if (!el.value) {
|
||||
return
|
||||
}
|
||||
|
||||
const script = document.createElement('script')
|
||||
script.setAttribute('type', 'text/javascript')
|
||||
script.setAttribute('src', 'https://cdn.carbonads.com/carbon.js?serve=CWYIVK3E&placement=uinuxtcom')
|
||||
script.setAttribute('id', '_carbonads_js')
|
||||
|
||||
el.value?.appendChild(script)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="el" class="carbon" />
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
@reference "../assets/css/main.css";
|
||||
|
||||
.carbon :deep(#carbonads) {
|
||||
@apply relative border border-(--ui-border) rounded-[calc(var(--ui-radius)*1.5)] hover:bg-(--ui-bg-elevated)/50 w-full transition-colors min-h-[220px] p-2;
|
||||
|
||||
.carbon-img {
|
||||
@apply flex justify-center w-full;
|
||||
|
||||
& > img {
|
||||
@apply !max-w-full w-full rounded-(--ui-radius);
|
||||
}
|
||||
}
|
||||
|
||||
.carbon-text {
|
||||
@apply text-sm text-(--ui-text-muted) transition-colors text-center text-pretty flex pt-2;
|
||||
}
|
||||
|
||||
.carbon-poweredby {
|
||||
@apply block text-[10px] text-center text-(--ui-text-dimmed) pt-2;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.carbon-text {
|
||||
@apply text-(--ui-text);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,7 +1,18 @@
|
||||
<template>
|
||||
<UBanner icon="i-lucide-construction" :actions="[{ label: 'Go to Nuxt UI v2', to: 'https://ui.nuxt.com', trailingIcon: 'i-lucide-arrow-right' }]" :close="false">
|
||||
<UBanner
|
||||
id="ui3-launch"
|
||||
icon="i-lucide-rocket"
|
||||
:actions="[
|
||||
{
|
||||
label: 'Discover Nuxt UI Pro',
|
||||
to: '/pro/pricing',
|
||||
trailingIcon: 'i-lucide-arrow-right'
|
||||
}
|
||||
]"
|
||||
close
|
||||
>
|
||||
<template #title>
|
||||
You're looking at the documentation for <span class="font-semibold">Nuxt UI v3</span>!
|
||||
<span class="font-semibold">Nuxt UI v3</span> is officially released.
|
||||
</template>
|
||||
</UBanner>
|
||||
</template>
|
||||
|
||||
51
docs/app/components/Countdown.vue
Normal file
51
docs/app/components/Countdown.vue
Normal file
@@ -0,0 +1,51 @@
|
||||
<script setup lang="ts">
|
||||
const endDate = new Date('2025-03-14T23:59:59Z')
|
||||
const second = 1000
|
||||
const minute = second * 60
|
||||
const hour = minute * 60
|
||||
const day = hour * 24
|
||||
|
||||
function getCountdown() {
|
||||
const distance = Math.floor((endDate.getTime() - Date.now()))
|
||||
return {
|
||||
day: Math.floor(distance / day),
|
||||
hour: Math.floor((distance % (day)) / (hour)),
|
||||
minute: Math.floor((distance % (hour)) / (minute)),
|
||||
second: Math.floor((distance % (minute)) / (second)),
|
||||
distance
|
||||
}
|
||||
}
|
||||
const countdown = ref(getCountdown())
|
||||
let interval: any
|
||||
if (countdown.value.distance > 0) {
|
||||
onMounted(() => {
|
||||
interval = setInterval(() => {
|
||||
countdown.value = getCountdown()
|
||||
if (countdown.value.distance <= 0) {
|
||||
clearInterval(interval)
|
||||
}
|
||||
}, 1000)
|
||||
})
|
||||
}
|
||||
const plural = (value: number) => (value === 1 ? '' : 's')
|
||||
const double = (value: number) => (value < 10 ? `0${value}` : value)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<p class="font-semibold text-gray-900 dark:text-white text-sm mb-3">
|
||||
Nuxt UI v3 launch offer ends in:
|
||||
</p>
|
||||
|
||||
<div class="flex items-center justify-center gap-2 text-center">
|
||||
<template v-for="(value, key) in countdown" :key="key">
|
||||
<div v-if="key !== 'distance'" class="flex flex-col items-center gap-2">
|
||||
<UBadge color="primary" class="w-14 h-14 font-bold text-2xl flex items-center justify-center tabular-nums" variant="subtle">
|
||||
{{ double(value) }}
|
||||
</UBadge>
|
||||
<span class="text-[10px] font-semibold text-gray-900 dark:text-white tracking-wide tabular-nums uppercase">{{ key }}{{ plural(value) }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -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>
|
||||
|
||||
@@ -30,14 +30,18 @@ const mobileLinks = computed(() => props.links.map(link => ({ ...link, defaultOp
|
||||
<UHeader :ui="{ left: 'min-w-0' }" :menu="{ shouldScaleBackground: true }">
|
||||
<template #left>
|
||||
<NuxtLink to="/" class="flex items-end gap-2 font-bold text-xl text-(--ui-text-highlighted) min-w-0 focus-visible:outline-(--ui-primary) shrink-0" aria-label="Nuxt UI">
|
||||
<LogoPro class="w-auto h-6 shrink-0 ui-pro-only" />
|
||||
<Logo class="w-auto h-6 shrink-0 ui-only" />
|
||||
<Logo v-if="route.path === '/'" class="w-auto h-6 shrink-0" />
|
||||
<LogoPro v-else-if="route.path.startsWith('/pro')" class="w-auto h-6 shrink-0" />
|
||||
<template v-else>
|
||||
<LogoPro class="w-auto h-6 shrink-0 ui-pro-only" />
|
||||
<Logo class="w-auto h-6 shrink-0 ui-only" />
|
||||
</template>
|
||||
</NuxtLink>
|
||||
|
||||
<UDropdownMenu
|
||||
v-slot="{ open }"
|
||||
:modal="false"
|
||||
:items="[{ label: `v${config.version}`, active: true, color: 'primary', checked: true, type: 'checkbox' }, { label: module === 'ui-pro' ? 'v1.5' : 'v2.19', to: module === 'ui-pro' ? 'https://ui.nuxt.com/pro' : 'https://ui.nuxt.com' }]"
|
||||
:items="[{ label: `v${config.version}`, active: true, color: 'primary', checked: true, type: 'checkbox' }, { label: module === 'ui-pro' ? 'v1.7.1' : 'v2.21.1', to: module === 'ui-pro' ? 'https://ui2.nuxt.com/pro' : 'https://ui2.nuxt.com' }]"
|
||||
:ui="{ content: 'w-(--reka-dropdown-menu-trigger-width) min-w-0' }"
|
||||
size="xs"
|
||||
>
|
||||
|
||||
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
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { upperFirst, camelCase } from 'scule'
|
||||
import { upperFirst, camelCase, kebabCase } from 'scule'
|
||||
import type { ComponentMeta } from 'vue-component-meta'
|
||||
import * as theme from '#build/ui'
|
||||
import * as themePro from '#build/ui-pro'
|
||||
@@ -112,7 +112,7 @@ const metaProps: ComputedRef<ComponentMeta['props']> = computed(() => {
|
||||
<ProseTd>
|
||||
<HighlightInlineType v-if="prop.type" :type="prop.type" />
|
||||
|
||||
<MDC v-if="prop.description" :value="prop.description" class="text-(--ui-text-toned) mt-1" />
|
||||
<MDC v-if="prop.description" :value="prop.description" class="text-(--ui-text-toned) mt-1" :cache-key="`${kebabCase(route.path)}-${prop.name}-description`" />
|
||||
|
||||
<ComponentPropsLinks v-if="prop.tags?.length" :prop="prop" />
|
||||
<ComponentPropsSchema v-if="prop.schema" :prop="prop" :ignore="ignore" />
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
<script setup lang="ts">
|
||||
import { kebabCase } from 'scule'
|
||||
import type { PropertyMeta } from 'vue-component-meta'
|
||||
|
||||
const props = defineProps<{
|
||||
prop: PropertyMeta
|
||||
}>()
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
const links = computed(() => props.prop.tags?.filter((tag: any) => tag.name === 'link'))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ProseUl v-if="links?.length">
|
||||
<ProseLi v-for="link in links" :key="link.name">
|
||||
<MDC :value="link.text ?? ''" class="my-1" />
|
||||
<ProseLi v-for="(link, index) in links" :key="index">
|
||||
<MDC :value="link.text ?? ''" class="my-1" :cache-key="`${kebabCase(route.path)}-${prop.name}-link-${index}`" />
|
||||
</ProseLi>
|
||||
</ProseUl>
|
||||
</template>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { kebabCase } from 'scule'
|
||||
import type { PropertyMeta } from 'vue-component-meta'
|
||||
|
||||
const props = defineProps<{
|
||||
@@ -6,6 +7,8 @@ const props = defineProps<{
|
||||
ignore?: string[]
|
||||
}>()
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
function getSchemaProps(schema: PropertyMeta['schema']): any {
|
||||
if (!schema || typeof schema === 'string' || !schema.schema) {
|
||||
return []
|
||||
@@ -40,7 +43,7 @@ const schemaProps = computed(() => {
|
||||
<ProseLi v-for="schemaProp in schemaProps" :key="schemaProp.name">
|
||||
<HighlightInlineType :type="`${schemaProp.name}${schemaProp.required === false ? '?' : ''}: ${schemaProp.type}`" />
|
||||
|
||||
<MDC v-if="schemaProp.description" :value="schemaProp.description" class="text-(--ui-text-muted) my-1" />
|
||||
<MDC v-if="schemaProp.description" :value="schemaProp.description" class="text-(--ui-text-muted) my-1" :cache-key="`${kebabCase(route.path)}-${prop.name}-${schemaProp.name}-description`" />
|
||||
</ProseLi>
|
||||
</ProseUl>
|
||||
</ProseCollapsible>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { upperFirst, camelCase } from 'scule'
|
||||
import { upperFirst, camelCase, kebabCase } from 'scule'
|
||||
|
||||
const props = defineProps<{
|
||||
prose?: boolean
|
||||
@@ -36,7 +36,7 @@ const meta = await fetchComponentMeta(name as any)
|
||||
<ProseTd>
|
||||
<HighlightInlineType v-if="slot.type" :type="slot.type" />
|
||||
|
||||
<MDC v-if="slot.description" :value="slot.description" class="text-(--ui-text-toned) mt-1" />
|
||||
<MDC v-if="slot.description" :value="slot.description" class="text-(--ui-text-toned) mt-1" :cache-key="`${kebabCase(route.path)}-${slot.name}-description`" />
|
||||
</ProseTd>
|
||||
</ProseTr>
|
||||
</ProseTbody>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { murmurHash } from 'ohash'
|
||||
import { hash } from 'ohash'
|
||||
|
||||
const props = defineProps<{
|
||||
type: string
|
||||
@@ -23,7 +23,7 @@ const type = computed(() => {
|
||||
return type
|
||||
})
|
||||
|
||||
const { data: ast } = await useAsyncData(`hightlight-inline-code-${murmurHash(type.value)}`, () => parseMarkdown(`\`${type.value}\`{lang="ts-type"}`))
|
||||
const { data: ast } = await useAsyncData(`hightlight-inline-code-${hash(type.value).slice(0, 10)}`, () => parseMarkdown(`\`${type.value}\`{lang="ts-type"}`))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -9,21 +9,22 @@ const props = withDefaults(defineProps<{
|
||||
|
||||
function getEmojiFlag(locale: string): string {
|
||||
const languageToCountry: Record<string, string> = {
|
||||
ar: 'sa',
|
||||
bn: 'bd',
|
||||
cs: 'cz',
|
||||
da: 'dk',
|
||||
el: 'gr',
|
||||
et: 'ee',
|
||||
en: 'gb',
|
||||
hi: 'in',
|
||||
ja: 'jp',
|
||||
km: 'kh',
|
||||
ko: 'kr',
|
||||
nb: 'no',
|
||||
sv: 'se',
|
||||
uk: 'ua',
|
||||
vi: 'vn'
|
||||
ar: 'sa', // Arabic -> Saudi Arabia
|
||||
bn: 'bd', // Bengali -> Bangladesh
|
||||
cs: 'cz', // Czech -> Czech Republic (note: modern country code is actually 'cz')
|
||||
da: 'dk', // Danish -> Denmark
|
||||
el: 'gr', // Greek -> Greece
|
||||
et: 'ee', // Estonian -> Estonia
|
||||
en: 'gb', // English -> Great Britain
|
||||
he: 'il', // Hebrew -> Israel
|
||||
hi: 'in', // Hindi -> India
|
||||
ja: 'jp', // Japanese -> Japan
|
||||
km: 'kh', // Khmer -> Cambodia
|
||||
ko: 'kr', // Korean -> South Korea
|
||||
nb: 'no', // Norwegian Bokmål -> Norway
|
||||
sv: 'se', // Swedish -> Sweden
|
||||
uk: 'ua', // Ukrainian -> Ukraine
|
||||
vi: 'vn' // Vietnamese -> Vietnam
|
||||
}
|
||||
|
||||
const baseLanguage = locale.split('-')[0]?.toLowerCase() || locale
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
const searchTerm = ref('')
|
||||
|
||||
const { data: users, status } = await useFetch('https://jsonplaceholder.typicode.com/users', {
|
||||
key: 'command-palette-users',
|
||||
transform: (data: { id: number, name: string, email: string }[]) => {
|
||||
return data?.map(user => ({ id: user.id, label: user.name, suffix: user.email, avatar: { src: `https://i.pravatar.cc/120?img=${user.id}` } })) || []
|
||||
},
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
const { data: users } = await useFetch('https://jsonplaceholder.typicode.com/users', {
|
||||
key: 'command-palette-users',
|
||||
transform: (data: { id: number, name: string, email: string }[]) => {
|
||||
return data?.map(user => ({ id: user.id, label: user.name, suffix: user.email, avatar: { src: `https://i.pravatar.cc/120?img=${user.id}` } })) || []
|
||||
},
|
||||
|
||||
@@ -3,6 +3,7 @@ const searchTerm = ref('')
|
||||
const searchTermDebounced = refDebounced(searchTerm, 200)
|
||||
|
||||
const { data: users, status } = await useFetch('https://jsonplaceholder.typicode.com/users', {
|
||||
key: 'command-palette-users',
|
||||
params: { q: searchTermDebounced },
|
||||
transform: (data: { id: number, name: string, email: string }[]) => {
|
||||
return data?.map(user => ({ id: user.id, label: user.name, suffix: user.email, avatar: { src: `https://i.pravatar.cc/120?img=${user.id}` } })) || []
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
const searchTerm = ref('')
|
||||
|
||||
const { data: users, status } = await useFetch('https://jsonplaceholder.typicode.com/users', {
|
||||
key: 'command-palette-users',
|
||||
params: { q: searchTerm },
|
||||
transform: (data: { id: number, name: string, email: string }[]) => {
|
||||
return data?.map(user => ({ id: user.id, label: user.name, suffix: user.email, avatar: { src: `https://i.pravatar.cc/120?img=${user.id}` } })) || []
|
||||
|
||||
@@ -22,7 +22,7 @@ async function onSubmit(event: FormSubmitEvent<Schema>) {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UForm :schema="v.safeParser(schema)" :state="state" class="space-y-4" @submit="onSubmit">
|
||||
<UForm :schema="schema" :state="state" class="space-y-4" @submit="onSubmit">
|
||||
<UFormField label="Email" name="email">
|
||||
<UInput v-model="state.email" />
|
||||
</UFormField>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
const { data: users, status } = await useFetch('https://jsonplaceholder.typicode.com/users', {
|
||||
key: 'typicode-users',
|
||||
transform: (data: { id: number, name: string }[]) => {
|
||||
return data?.map(user => ({
|
||||
label: user.name,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
const { data: users, status } = await useFetch('https://jsonplaceholder.typicode.com/users', {
|
||||
key: 'typicode-users-email',
|
||||
transform: (data: { id: number, name: string, email: string }[]) => {
|
||||
return data?.map(user => ({
|
||||
label: user.name,
|
||||
|
||||
@@ -3,6 +3,7 @@ const searchTerm = ref('')
|
||||
const searchTermDebounced = refDebounced(searchTerm, 200)
|
||||
|
||||
const { data: users, status } = await useFetch('https://jsonplaceholder.typicode.com/users', {
|
||||
key: 'typicode-users',
|
||||
params: { q: searchTermDebounced },
|
||||
transform: (data: { id: number, name: string }[]) => {
|
||||
return data?.map(user => ({
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
const searchTerm = ref('')
|
||||
|
||||
const { data: users, status } = await useFetch('https://jsonplaceholder.typicode.com/users', {
|
||||
key: 'command-palette-users',
|
||||
params: { q: searchTerm },
|
||||
transform: (data: { id: number, name: string, email: string }[]) => {
|
||||
return data?.map(user => ({ id: user.id, label: user.name, suffix: user.email, avatar: { src: `https://i.pravatar.cc/120?img=${user.id}` } })) || []
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
const { data: users, status } = await useFetch('https://jsonplaceholder.typicode.com/users', {
|
||||
key: 'typicode-users',
|
||||
transform: (data: { id: number, name: string }[]) => {
|
||||
return data?.map(user => ({
|
||||
label: user.name,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
const { data: users, status } = await useFetch('https://jsonplaceholder.typicode.com/users', {
|
||||
key: 'typicode-users-email',
|
||||
transform: (data: { id: number, name: string, email: string }[]) => {
|
||||
return data?.map(user => ({
|
||||
label: user.name,
|
||||
|
||||
@@ -3,6 +3,7 @@ const searchTerm = ref('')
|
||||
const searchTermDebounced = refDebounced(searchTerm, 200)
|
||||
|
||||
const { data: users, status } = await useFetch('https://jsonplaceholder.typicode.com/users', {
|
||||
key: 'typicode-users',
|
||||
params: { q: searchTermDebounced },
|
||||
transform: (data: { id: number, name: string }[]) => {
|
||||
return data?.map(user => ({
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
const { data: users, status } = await useFetch('https://jsonplaceholder.typicode.com/users', {
|
||||
key: 'typicode-users',
|
||||
transform: (data: { id: number, name: string }[]) => {
|
||||
return data?.map(user => ({
|
||||
label: user.name,
|
||||
|
||||
@@ -13,6 +13,7 @@ type User = {
|
||||
}
|
||||
|
||||
const { data, status } = await useFetch<User[]>('https://jsonplaceholder.typicode.com/users', {
|
||||
key: 'table-users',
|
||||
transform: (data) => {
|
||||
return data?.map(user => ({
|
||||
...user,
|
||||
|
||||
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>
|
||||
@@ -3,7 +3,7 @@ withDefaults(defineProps<{
|
||||
title: string
|
||||
description: string
|
||||
component: string
|
||||
module: string
|
||||
module?: string
|
||||
}>(), {
|
||||
module: ''
|
||||
})
|
||||
|
||||
@@ -3,8 +3,8 @@ withDefaults(defineProps<{
|
||||
title: string
|
||||
description: string
|
||||
headline: string
|
||||
framework: string
|
||||
module: string
|
||||
framework?: string
|
||||
module?: string
|
||||
}>(), {
|
||||
framework: 'nuxt',
|
||||
module: ''
|
||||
|
||||
@@ -37,6 +37,10 @@ export const useContentNavigation = (navigation: Ref<ContentNavigationItem[] | u
|
||||
return {
|
||||
...item,
|
||||
children: item.children?.filter((child: any) => {
|
||||
if (child.path.startsWith('/components')) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (child.framework && child.framework !== framework.value) {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -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,6 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import colors from 'tailwindcss/colors'
|
||||
// import { debounce } from 'perfect-debounce'
|
||||
import type { NuxtError } from '#app'
|
||||
|
||||
const props = defineProps<{
|
||||
@@ -15,16 +14,6 @@ const { data: files } = useLazyAsyncData('search', () => queryCollectionSearchSe
|
||||
server: false
|
||||
})
|
||||
|
||||
const searchTerm = ref('')
|
||||
|
||||
// watch(searchTerm, debounce((query: string) => {
|
||||
// if (!query) {
|
||||
// return
|
||||
// }
|
||||
|
||||
// useTrackEvent('Search', { props: { query: `${query} - ${searchTerm.value?.commandPaletteRef.results.length} results` } })
|
||||
// }, 500))
|
||||
|
||||
const links = useLinks()
|
||||
const color = computed(() => colorMode.value === 'dark' ? (colors as any)[appConfig.ui.colors.neutral][900] : 'white')
|
||||
const radius = computed(() => `:root { --ui-radius: ${appConfig.theme.radius}rem; }`)
|
||||
@@ -48,7 +37,7 @@ useHead({
|
||||
})
|
||||
|
||||
useSeoMeta({
|
||||
titleTemplate: '%s - Nuxt UI v3',
|
||||
titleTemplate: '%s - Nuxt UI',
|
||||
title: String(props.error.statusCode)
|
||||
})
|
||||
|
||||
@@ -67,17 +56,16 @@ provide('navigation', mappedNavigation)
|
||||
<UApp>
|
||||
<NuxtLoadingIndicator color="#FFF" />
|
||||
|
||||
<!-- <Banner /> -->
|
||||
<Banner />
|
||||
|
||||
<Header :links="links" />
|
||||
|
||||
<UError :error="error" />
|
||||
|
||||
<!-- <Footer /> -->
|
||||
<Footer />
|
||||
|
||||
<ClientOnly>
|
||||
<LazyUContentSearch
|
||||
v-model:search-term="searchTerm"
|
||||
:files="files"
|
||||
:groups="[{
|
||||
id: 'framework',
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
<template>
|
||||
<div>
|
||||
<slot />
|
||||
</div>
|
||||
<slot />
|
||||
</template>
|
||||
|
||||
182
docs/app/pages/.index.yml
Normal file
182
docs/app/pages/.index.yml
Normal file
@@ -0,0 +1,182 @@
|
||||
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/nuxt
|
||||
class: 'ui-only nuxt-only'
|
||||
- label: Get started
|
||||
to: /getting-started/installation/vue
|
||||
class: 'ui-only vue-only'
|
||||
- label: Get started
|
||||
to: /getting-started/installation/pro/nuxt
|
||||
class: 'ui-pro-only nuxt-only'
|
||||
- label: Get started
|
||||
to: /getting-started/installation/pro/vue
|
||||
class: 'ui-pro-only vue-only'
|
||||
- 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: Customize design system
|
||||
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: Customize components
|
||||
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
|
||||
@@ -1,4 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { kebabCase } from 'scule'
|
||||
import type { ContentNavigationItem } from '@nuxt/content'
|
||||
import { findPageBreadcrumb, mapContentNavigation } from '#ui-pro/utils/content'
|
||||
|
||||
@@ -9,7 +10,7 @@ definePageMeta({
|
||||
layout: 'docs'
|
||||
})
|
||||
|
||||
const { data: page } = await useAsyncData(route.path, () => queryCollection('content').path(route.path).first())
|
||||
const { data: page } = await useAsyncData(kebabCase(route.path), () => queryCollection('content').path(route.path).first())
|
||||
if (!page.value) {
|
||||
throw createError({ statusCode: 404, statusMessage: 'Page not found', fatal: true })
|
||||
}
|
||||
@@ -24,7 +25,7 @@ watch(page, () => {
|
||||
}
|
||||
}, { immediate: true })
|
||||
|
||||
const { data: surround } = await useAsyncData(`${route.path}-surround`, () => {
|
||||
const { data: surround } = await useAsyncData(`${kebabCase(route.path)}-surround`, () => {
|
||||
return queryCollectionItemSurroundings('content', route.path, {
|
||||
fields: ['description']
|
||||
}).orWhere(group => group.where('framework', '=', framework.value).where('framework', 'IS NULL'))
|
||||
@@ -81,6 +82,8 @@ if (route.path.startsWith('/components')) {
|
||||
})
|
||||
} else {
|
||||
defineOgImageComponent('Docs', {
|
||||
title: page.value.title,
|
||||
description: page.value.description,
|
||||
headline: breadcrumb.value?.[breadcrumb.value.length - 1]?.label || 'Nuxt UI',
|
||||
framework: page.value?.framework,
|
||||
module: page.value.module
|
||||
@@ -106,23 +109,6 @@ const communityLinks = computed(() => [{
|
||||
icon: 'i-lucide-map',
|
||||
to: '/roadmap'
|
||||
}])
|
||||
|
||||
// const resourcesLinks = [{
|
||||
// icon: 'i-simple-icons-figma',
|
||||
// label: 'Figma Kit',
|
||||
// to: 'https://www.figma.com/community/file/1288455405058138934',
|
||||
// target: '_blank'
|
||||
// }, {
|
||||
// label: 'Playground',
|
||||
// icon: 'i-simple-icons-stackblitz',
|
||||
// to: 'https://stackblitz.com/edit/nuxt-ui',
|
||||
// target: '_blank'
|
||||
// }, {
|
||||
// icon: 'i-simple-icons-nuxtdotjs',
|
||||
// label: 'Nuxt docs',
|
||||
// to: 'https://nuxt.com',
|
||||
// target: '_blank'
|
||||
// }]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -137,7 +123,7 @@ const communityLinks = computed(() => [{
|
||||
</template>
|
||||
|
||||
<template #description>
|
||||
<MDC v-if="page.description" :value="page.description" unwrap="p" />
|
||||
<MDC v-if="page.description" :value="page.description" unwrap="p" :cache-key="`${kebabCase(route.path)}-description`" />
|
||||
</template>
|
||||
|
||||
<template v-if="page.links?.length" #links>
|
||||
@@ -171,14 +157,9 @@ const communityLinks = computed(() => [{
|
||||
|
||||
<UPageLinks title="Community" :links="communityLinks" />
|
||||
|
||||
<!-- <USeparator type="dashed" />
|
||||
|
||||
<UPageLinks title="Resources" :links="resourcesLinks" />
|
||||
|
||||
<USeparator type="dashed" />
|
||||
|
||||
<AdsPro />
|
||||
<AdsCarbon /> -->
|
||||
<AdsCarbon />
|
||||
</template>
|
||||
</UContentToc>
|
||||
</template>
|
||||
|
||||
@@ -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,9 @@ 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" />
|
||||
<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" />
|
||||
</UPageHero>
|
||||
|
||||
<div v-for="category in categories" :key="category.id">
|
||||
@@ -148,6 +151,7 @@ onMounted(() => {
|
||||
:description="component.description"
|
||||
:to="component.path"
|
||||
:ui="{ wrapper: 'order-last', container: 'lg:flex' }"
|
||||
class="group"
|
||||
>
|
||||
<template #title>
|
||||
<div class="flex items-center gap-0.5">
|
||||
@@ -156,7 +160,7 @@ onMounted(() => {
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="group rounded-[calc(var(--ui-radius)*1.5)] border border-(--ui-border-muted) overflow-hidden aspect-[16/9]">
|
||||
<div class="rounded-[calc(var(--ui-radius)*1.5)] border border-(--ui-border-muted) overflow-hidden aspect-[16/9]">
|
||||
<UColorModeImage
|
||||
:light="`${component.path.replace('/components/', '/components/light/')}.png`"
|
||||
:dark="`${component.path.replace('/components/', '/components/dark/')}.png`"
|
||||
|
||||
@@ -8,7 +8,6 @@ hero:
|
||||
links:
|
||||
- label: Purchase Pro Kit
|
||||
to: 'https://nuxt.lemonsqueezy.com/buy/17213c49-621b-4c2c-9478-3a50a099003d'
|
||||
trailing-icon: i-lucide-arrow-right
|
||||
target: _blank
|
||||
- label: Free Figma Kit
|
||||
to: 'https://go.nuxt.com/figma'
|
||||
@@ -121,7 +120,6 @@ section4:
|
||||
links:
|
||||
- label: Get access now
|
||||
to: 'https://nuxt.lemonsqueezy.com/buy/17213c49-621b-4c2c-9478-3a50a099003d'
|
||||
trailing-icon: i-lucide-arrow-right
|
||||
- label: Preview UI Pro Design Kit
|
||||
to: 'https://go.nuxt.com/figma-pro'
|
||||
icon: i-logos-figma
|
||||
@@ -180,6 +178,7 @@ pricing:
|
||||
- title: Solo License
|
||||
description: Design faster with all Nuxt UI Pro components.
|
||||
price: $149
|
||||
# discount: $119
|
||||
billing_period: one-time payment
|
||||
billing_cycle: plus local taxes
|
||||
class: bg-(--ui-bg-elevated)/50
|
||||
@@ -201,6 +200,7 @@ pricing:
|
||||
- title: Team License
|
||||
description: Everything you need to deliver faster as a team.
|
||||
price: $349
|
||||
# discount: $279
|
||||
billing_period: one-time payment
|
||||
billing_cycle: plus local taxes
|
||||
class: bg-(--ui-bg-elevated)/50
|
||||
@@ -277,16 +277,14 @@ faq:
|
||||
content: As the Figma Pro Kit is a digital product packaged as a zip file, we cannot offer refunds once the purchase is made.
|
||||
- label: Do you have a Figma to Code plugin?
|
||||
content: >
|
||||
We recommend the open source [TeamPad Dev](https://github.com/ecomfe/tempad-dev) inspect panel with the [TeamPad Dev Nuxt UI Plugin](https://github.com/Justineo/tempad-dev-plugin-nuxt-ui):
|
||||
We recommend the open source [TemPad Dev](https://github.com/ecomfe/tempad-dev) inspect panel with the [TemPad Dev Nuxt UI Plugin](https://github.com/Justineo/tempad-dev-plugin-nuxt-ui):
|
||||
|
||||
1. Install the [TeamPad Dev Chrome Extension](https://chromewebstore.google.com/detail/tempad-dev/lgoeakbaikpkihoiphamaeopmliaimpc)
|
||||
1. Install the [TemPad Dev Chrome Extension](https://chromewebstore.google.com/detail/tempad-dev/lgoeakbaikpkihoiphamaeopmliaimpc)
|
||||
|
||||
2. Open your Figma file with Nuxt UI components (reload the page if you don't see the TeamPad Dev panel)
|
||||
2. Open your Figma file with Nuxt UI components (reload the page if you don't see the TemPad Dev panel)
|
||||
|
||||
3. Install the `@nuxt` in TeamPad Dev's plugins section
|
||||
3. Install the `@nuxt` (or `@nuxt/pro` for Nuxt UI Pro) in TemPad Dev's plugins section
|
||||
|
||||
4. Select any Nuxt UI component and inspect the code it generates
|
||||
|
||||
{.w-full .rounded .mb-2 .max-w-[636px]}
|
||||
|
||||
*Right now, only Nuxt UI components are supported, but the code of the plugin is [open source](https://github.com/Justineo/tempad-dev-plugin-nuxt-ui) and anyone can contribute to it.*
|
||||
{.w-full .rounded .mb-2 .max-w-[636px]}
|
||||
|
||||
@@ -93,10 +93,10 @@ onMounted(async () => {
|
||||
}"
|
||||
>
|
||||
<template #title>
|
||||
<MDC :value="page.hero.title" unwrap="p" />
|
||||
<MDC :value="page.hero.title" unwrap="p" cache-key="figma-hero-title" />
|
||||
</template>
|
||||
<template #description>
|
||||
<MDC :value="page.hero.description" unwrap="p" />
|
||||
<MDC :value="page.hero.description" unwrap="p" cache-key="figma-hero-description" />
|
||||
</template>
|
||||
<!-- <img src="/pro/figma/nuxt-ui-figma.png" alt="Screnshot of the Nuxt UI Figma design kit" class="w-full h-auto border border-(--ui-border) border-b-0"> -->
|
||||
<div class="relative">
|
||||
@@ -140,10 +140,10 @@ onMounted(async () => {
|
||||
class="rounded-none bg-gradient-to-b from-(--ui-bg-muted) to-(--ui-bg)"
|
||||
>
|
||||
<template #title>
|
||||
<MDC :value="page.cta1.title" unwrap="p" />
|
||||
<MDC :value="page.cta1.title" unwrap="p" cache-key="figma-cta-1-title" />
|
||||
</template>
|
||||
<template #description>
|
||||
<MDC :value="page.cta1.description" unwrap="p" />
|
||||
<MDC :value="page.cta1.description" unwrap="p" cache-key="figma-cta-1-description" />
|
||||
</template>
|
||||
</UPageCTA>
|
||||
<UPageSection v-bind="page.section1" orientation="horizontal" :ui="{ container: 'py-16 sm:py-16 lg:py-16' }">
|
||||
@@ -189,7 +189,7 @@ onMounted(async () => {
|
||||
}"
|
||||
>
|
||||
<template #description>
|
||||
<MDC :value="page.section4.description" unwrap="p" />
|
||||
<MDC :value="page.section4.description" unwrap="p" cache-key="figma-section-4-description" />
|
||||
</template>
|
||||
<div aria-hidden="true" class="absolute z-[-1] border-x border-(--ui-border) inset-0 mx-4 sm:mx-6 lg:mx-8" />
|
||||
<ul class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 items-start justify-center border border-(--ui-border) border-b-0 sm:divide-x divide-y lg:divide-y-0 divide-(--ui-border)">
|
||||
@@ -233,6 +233,7 @@ onMounted(async () => {
|
||||
:title="plan.title"
|
||||
:description="plan.description"
|
||||
:price="plan.price"
|
||||
:discount="plan.discount"
|
||||
:billing-period="plan.billing_period"
|
||||
:billing-cycle="plan.billing_cycle"
|
||||
:highlight="plan.highlight"
|
||||
@@ -245,7 +246,7 @@ onMounted(async () => {
|
||||
<template #features>
|
||||
<li v-for="(feature, i) in plan.features" :key="i" class="flex items-center gap-2 min-w-0">
|
||||
<UIcon name="i-lucide-circle-check" class="size-5 shrink-0 text-(--ui-primary)" />
|
||||
<MDC :value="feature" unwrap="p" tag="span" class="text-sm truncate text-(--ui-text-accented)" />
|
||||
<MDC :value="feature" unwrap="p" tag="span" class="text-sm truncate text-(--ui-text-accented)" :cache-key="`figma-pricing-plan-${index}-feature-${i}`" />
|
||||
</li>
|
||||
</template>
|
||||
<template #button>
|
||||
@@ -281,8 +282,8 @@ onMounted(async () => {
|
||||
:items="(page.faq.items as any[])"
|
||||
class="max-w-4xl mx-auto"
|
||||
>
|
||||
<template #body="{ item }">
|
||||
<MDC :value="item.content" unwrap="p" />
|
||||
<template #body="{ item, index }">
|
||||
<MDC :value="item.content" unwrap="p" :cache-key="`figma-faq-${index}-content`" />
|
||||
</template>
|
||||
</UPageAccordion>
|
||||
</UPageSection>
|
||||
|
||||
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)' }"
|
||||
:while-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)' }"
|
||||
:while-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" cache-key="index-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" cache-key="index-component-customization-description" />
|
||||
</template>
|
||||
|
||||
<MDC :value="page.component_customization.code" cache-key="index-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>
|
||||
@@ -17,7 +17,10 @@ pricing:
|
||||
title: Figma Kit Pro
|
||||
description: Get all Nuxt UI Pro components in a Figma kit to design your next application before coding. Everything you need, from wire-framing to high-fidelity web integration.
|
||||
orientation: horizontal
|
||||
price: $149 - $349
|
||||
price: $149
|
||||
# discount: $119
|
||||
billing_period: one-time payment
|
||||
billing_cycle: plus local taxes
|
||||
terms: Solo & Team licenses available.
|
||||
features:
|
||||
- 1700+ components & variants from Nuxt UI & UI Pro
|
||||
@@ -29,13 +32,14 @@ pricing:
|
||||
- Free [preview available](https://www.figma.com/design/mxXR9binOSLU3rYKZZRPXs/PREVIEW---NuxtUIPro-V3-BETA?m=auto&t=c4598Wr0rZwKPs5M-1)
|
||||
- Includes Nuxt UI [Figma Kit](https://www.figma.com/community/file/1288455405058138934)
|
||||
button:
|
||||
label: Explore Figma Kit Pricing
|
||||
label: Buy Figma Kit
|
||||
to: 'https://nuxt.lemonsqueezy.com/buy/17213c49-621b-4c2c-9478-3a50a099003d'
|
||||
color: 'neutral'
|
||||
plans:
|
||||
- title: Solo
|
||||
description: Tailored for indie hackers, freelancers and solo founders.
|
||||
price: $249
|
||||
# discount: $199
|
||||
billing_period: one-time payment
|
||||
billing_cycle: plus local taxes
|
||||
features:
|
||||
@@ -50,6 +54,7 @@ pricing:
|
||||
- title: Startup
|
||||
description: Best suited for small teams, startups and agencies.
|
||||
price: $499
|
||||
# discount: $399
|
||||
billing_period: one-time payment
|
||||
billing_cycle: plus local taxes
|
||||
features:
|
||||
@@ -65,6 +70,7 @@ pricing:
|
||||
- title: Organization
|
||||
description: Ideal for larger teams and organizations.
|
||||
price: $999
|
||||
# discount: $799
|
||||
billing_period: one-time payment
|
||||
billing_cycle: plus local taxes
|
||||
features:
|
||||
@@ -174,7 +180,7 @@ testimonials:
|
||||
- quote: "Nuxt UI Pro is my preferred choice for everything, from a POC to a web platform. It's ready to use out-of-the-box and assists me in crafting pixel-perfect UIs. It saves me a significant amount of time while remaining highly customizable. Give it a try, and you won't be let down."
|
||||
user:
|
||||
name: 'Estéban Soubiran'
|
||||
description: 'Web developer and UnJS member'
|
||||
description: 'Software engineer'
|
||||
to: 'https://x.com/soubiran_'
|
||||
target: _blank
|
||||
avatar:
|
||||
|
||||
@@ -7,13 +7,13 @@ hero:
|
||||
- label: Buy a license
|
||||
size: xl
|
||||
to: /pro/pricing
|
||||
trailing-icon: i-lucide-arrow-right
|
||||
- label: Get started
|
||||
- label: Try for free
|
||||
trailing: true
|
||||
color: neutral
|
||||
variant: ghost
|
||||
variant: outline
|
||||
to: /getting-started/installation/pro/nuxt
|
||||
size: xl
|
||||
trailingIcon: i-lucide-arrow-right
|
||||
features:
|
||||
title: Create stunning Vue applications faster.
|
||||
description: Nuxt UI Pro comes packed with powerful features to help you build modern, performant, accessible and responsive Nuxt applications at record speed. From pre-built UI sections to Figma design kits, every detail is crafted to speed up your development and deliver a polished user experience.
|
||||
@@ -157,8 +157,8 @@ cta:
|
||||
links:
|
||||
- label: Buy a license
|
||||
to: '/pro/pricing'
|
||||
trailing-icon: i-lucide-arrow-right
|
||||
- label: Get started
|
||||
to: '/getting-started/license'
|
||||
variant: ghost
|
||||
- label: Try for free
|
||||
to: /getting-started/installation/pro/nuxt
|
||||
variant: outline
|
||||
color: neutral
|
||||
trailingIcon: i-lucide-arrow-right
|
||||
|
||||
@@ -5,16 +5,16 @@ hero:
|
||||
description: 'Ready to use templates powered by our premium Vue components and Nuxt Content.<br class="hidden lg:block"> The templates are responsive, accessible and easy to customize so you can get started in no time.'
|
||||
navigation: false
|
||||
links:
|
||||
- label: Get started
|
||||
to: /getting-started/installation/pro/nuxt#use-an-official-template
|
||||
color: neutral
|
||||
- label: Buy a license
|
||||
color: primary
|
||||
size: xl
|
||||
trailingIcon: i-lucide-arrow-right
|
||||
- label: Purchase a license
|
||||
to: /pro/pricing
|
||||
- label: Try for free
|
||||
to: /getting-started/installation/pro/nuxt#use-an-official-template
|
||||
size: xl
|
||||
color: neutral
|
||||
variant: outline
|
||||
to: /pro/pricing
|
||||
trailingIcon: i-lucide-arrow-right
|
||||
templates:
|
||||
- title: 'Dashboard'
|
||||
description: "A template to illustrate how to build your own dashboard with the 15+ latest Nuxt UI Pro components, designed specifically to create a consistent look and feel."
|
||||
@@ -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" cache-key="pro-hero-title" />
|
||||
</template>
|
||||
<template #description>
|
||||
<MDC :value="page.hero.description" unwrap="p" />
|
||||
</template>
|
||||
<template #top>
|
||||
<StarsBg />
|
||||
<MDC :value="page.hero.description" tag="span" unwrap="p" cache-key="pro-hero-description" />
|
||||
</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>
|
||||
@@ -83,11 +82,11 @@ 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] " />
|
||||
<Motion :initial="{ opacity: 0, transform: 'translateY(10px)' }" :while-in-view="{ opacity: 1, transform: 'translateY(0)' }" :in-view-options="{ once: true }" :transition="{ delay: 0.2 }">
|
||||
<MDC :value="page.testimonial.quote" tag="span" unwrap="p" class="before:content-[open-quote] after:content-[close-quote]" cache-key="pro-testimonial-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 }">
|
||||
<Motion :initial="{ opacity: 0, transform: 'translateY(10px)' }" :while-in-view="{ opacity: 1, transform: 'translateY(0)' }" :in-view-options="{ once: true }" :transition="{ delay: 0.3 }">
|
||||
<UUser
|
||||
v-bind="page.testimonial.user"
|
||||
class="justify-center"
|
||||
@@ -104,7 +103,7 @@ useSeoMeta({
|
||||
}"
|
||||
class="border-t border-(--ui-border)"
|
||||
>
|
||||
<Motion as-child :initial="{ height: 0 }" :in-view="{ height: 'auto' }" :transition="{ delay: 0.4, duration: 1 }">
|
||||
<Motion as-child :initial="{ height: 0 }" :while-in-view="{ height: 'auto' }" :transition="{ delay: 0.4, 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>
|
||||
</UPageSection>
|
||||
@@ -117,13 +116,13 @@ useSeoMeta({
|
||||
wrapper: 'grid grid-cols-1 lg:grid-cols-2',
|
||||
description: 'lg:mt-0' }"
|
||||
orientation="horizontal"
|
||||
class="rounded-none border-t border-(--ui-border) bg-gradient-to-b from-(--ui-bg-muted) to-(--ui-bg)"
|
||||
class="rounded-none border-t border-(--ui-border) bg-gradient-to-b from-(--ui-bg-elevated)/50 to-(--ui-bg)"
|
||||
>
|
||||
<template #title>
|
||||
<MDC :value="page.mainSection.title" unwrap="p" />
|
||||
<MDC :value="page.mainSection.title" tag="span" unwrap="p" cache-key="pro-main-section-title" />
|
||||
</template>
|
||||
<template #description>
|
||||
<MDC :value="page.mainSection.description" unwrap="p" />
|
||||
<MDC :value="page.mainSection.description" tag="span" unwrap="p" cache-key="pro-main-section-description" />
|
||||
</template>
|
||||
</UPageCTA>
|
||||
<UPageSection
|
||||
@@ -140,7 +139,7 @@ useSeoMeta({
|
||||
container: index === 0 ? 'pb-0 sm:pb-0 lg:pb-0 py-16 sm:py-16 lg:py-16' : ''
|
||||
}"
|
||||
>
|
||||
<MDC :value="section.code" />
|
||||
<MDC :value="section.code" :cache-key="`pro-section-${index}-code`" />
|
||||
</UPageSection>
|
||||
|
||||
<UPageSection
|
||||
@@ -198,6 +197,7 @@ useSeoMeta({
|
||||
orientation="horizontal"
|
||||
>
|
||||
<StarsBg />
|
||||
|
||||
<video
|
||||
class="rounded-[var(--ui-radius)] z-10"
|
||||
preload="none"
|
||||
|
||||
@@ -24,11 +24,10 @@ useSeoMeta({
|
||||
}"
|
||||
>
|
||||
<template #title>
|
||||
<MDC :value="page.pricing.title" unwrap="p" />
|
||||
</template>
|
||||
<template #top>
|
||||
<StarsBg />
|
||||
<MDC :value="page.pricing.title" unwrap="p" cache-key="pro-pricing-title" />
|
||||
</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
|
||||
@@ -43,6 +42,7 @@ useSeoMeta({
|
||||
:title="plan.title"
|
||||
:description="plan.description"
|
||||
:price="plan.price"
|
||||
:discount="plan.discount"
|
||||
:billing-period="plan.billing_period"
|
||||
:billing-cycle="plan.billing_cycle"
|
||||
:variant="plan.highlight ? 'soft' : 'outline'"
|
||||
@@ -54,12 +54,14 @@ useSeoMeta({
|
||||
<UPricingPlan
|
||||
v-bind="page.pricing.figma"
|
||||
variant="naked"
|
||||
:billing-period="page.pricing.figma.billing_period"
|
||||
:billing-cycle="page.pricing.figma.billing_cycle"
|
||||
class="lg:rounded-none border lg:border-y-0 border-(--ui-border)"
|
||||
>
|
||||
<template #features>
|
||||
<li v-for="(feature, index) in page.pricing.figma.features" :key="index" class="flex items-center gap-2 min-w-0">
|
||||
<UIcon name="i-lucide-circle-check" class="size-5 text-(--ui-primary) shrink-0" />
|
||||
<MDC :value="feature" unwrap="p" class="text-sm truncate text-(--ui-text-toned)" />
|
||||
<MDC :value="feature" unwrap="p" class="text-sm truncate text-(--ui-text-toned)" :cache-key="`pro-pricing-figma-feature-${index}`" />
|
||||
</li>
|
||||
</template>
|
||||
</UPricingPlan>
|
||||
@@ -111,8 +113,8 @@ useSeoMeta({
|
||||
:items="(page.faq.items as any[])"
|
||||
class="max-w-4xl mx-auto"
|
||||
>
|
||||
<template #body="{ item }">
|
||||
<MDC :value="item.content" unwrap="p" />
|
||||
<template #body="{ item, index }">
|
||||
<MDC :value="item.content" unwrap="p" :cache-key="`pro-pricing-faq-${index}-content`" />
|
||||
</template>
|
||||
</UPageAccordion>
|
||||
</UPageSection>
|
||||
|
||||
@@ -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" cache-key="pro-templates-hero-title" />
|
||||
</template>
|
||||
|
||||
<template #description>
|
||||
<MDC :value="page.hero.description" unwrap="p" />
|
||||
</template>
|
||||
</UPageHero>
|
||||
<template #description>
|
||||
<MDC :value="page.hero.description" unwrap="p" cache-key="pro-templates-hero-description" />
|
||||
</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" :cache-key="`pro-templates-${index}-description`" />
|
||||
</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)' }" :while-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>
|
||||
|
||||
@@ -3,7 +3,7 @@ const title = 'Roadmap'
|
||||
const description = 'Discover our Volta board for @nuxt/ui development status.'
|
||||
|
||||
useSeoMeta({
|
||||
titleTemplate: '%s - Nuxt UI v3',
|
||||
titleTemplate: '%s - Nuxt UI',
|
||||
title,
|
||||
ogTitle: 'Nuxt UI Roadmap',
|
||||
description
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
---
|
||||
navigation.title: Introduction
|
||||
title: Nuxt UI v3
|
||||
description: 'A comprehensive, Nuxt-integrated UI library providing a rich set of fully-styled, accessible and highly customizable components for building modern web applications.'
|
||||
title: Introduction
|
||||
description: 'Nuxt UI harnesses the combined strengths of Reka UI, Tailwind CSS, and Tailwind Variants to offer developers an unparalleled set of tools for creating sophisticated, accessible, and highly performant user interfaces.'
|
||||
navigation.icon: i-lucide-house
|
||||
---
|
||||
|
||||
We're thrilled to introduce this major update to our UI library, bringing significant improvements and powerful new features. Nuxt UI v3 represents a leap forward in creating robust, accessible, and highly customizable user interfaces for Nuxt applications.
|
||||
|
||||
## What's New in v3?
|
||||
|
||||
<iframe width="100%" height="100%" src="https://www.youtube-nocookie.com/embed/_eQxomah-nA?si=pDSzchUBDKb2NQu7" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen style="aspect-ratio: 16/9;" class="rounded-[calc(var(--ui-radius)*1.5)]"></iframe>
|
||||
|
||||
### Reka UI
|
||||
@@ -24,7 +19,7 @@ This transition empowers Nuxt UI to become a more comprehensive and flexible UI
|
||||
|
||||
### Tailwind CSS v4
|
||||
|
||||
Nuxt UI v3 integrates the latest Tailwind CSS v4, bringing significant improvements:
|
||||
Nuxt UI integrates the latest Tailwind CSS v4, bringing significant improvements:
|
||||
|
||||
- **Built for performance**: Full builds in the new engine are up to 5x faster, and incremental builds are over 100x faster — and measured in microseconds.
|
||||
- **Unified toolchain**: Built-in import handling, vendor prefixing, and syntax transforms, with no additional tooling required.
|
||||
@@ -47,7 +42,7 @@ This integration unifies the styling of components, ensuring consistency and cod
|
||||
|
||||
### TypeScript Integration
|
||||
|
||||
Nuxt UI v3 offers significantly improved TypeScript integration, providing a superior developer experience:
|
||||
Nuxt UI offers significantly improved TypeScript integration, providing a superior developer experience:
|
||||
|
||||
- **Enhanced Auto-completion**:
|
||||
- Full auto-completion for component props based on your theme
|
||||
@@ -112,11 +107,7 @@ Key points to consider:
|
||||
## FAQ
|
||||
|
||||
::accordion
|
||||
::accordion-item{label="What are the main considerations when upgrading to Nuxt UI v3?"}
|
||||
The transition to v3 involves significant changes, including new component structures, updated theming approaches, and revised TypeScript definitions. We recommend a careful, incremental upgrade process, starting with thorough testing in a development environment.
|
||||
::
|
||||
|
||||
::accordion-item{label="Is Nuxt UI v3 compatible with standalone Vue projects?"}
|
||||
::accordion-item{label="Is Nuxt UI compatible with standalone Vue projects?"}
|
||||
Nuxt UI is now compatible with Vue! You can follow the [installation guide](/getting-started/installation/vue) to get started.
|
||||
::
|
||||
|
||||
@@ -124,23 +115,19 @@ Key points to consider:
|
||||
We've also rebuilt Nuxt UI Pro from scratch as v3 to match Nuxt UI version. The license you bought or will buy is valid for both Nuxt UI Pro v1 and v3, this is a **free update**. You can follow the [installation guide](/getting-started/installation/pro/nuxt) to get started.
|
||||
::
|
||||
|
||||
::accordion-item{label="Will Nuxt UI v3 work with other CSS frameworks like UnoCSS?"}
|
||||
Nuxt UI v3 is currently designed to work exclusively with Tailwind CSS. While there's interest in UnoCSS support, implementing it would require significant changes to the theme structure due to differences in class naming conventions. As a result, we don't have plans to add UnoCSS support in v3.
|
||||
::accordion-item{label="Will Nuxt UI work with other CSS frameworks like UnoCSS?"}
|
||||
Nuxt UI is currently designed to work exclusively with Tailwind CSS. While there's interest in UnoCSS support, implementing it would require significant changes to the theme structure due to differences in class naming conventions. As a result, we don't have plans to add UnoCSS support.
|
||||
::
|
||||
|
||||
::accordion-item{label="How does Nuxt UI v3 handle accessibility?"}
|
||||
Nuxt UI v3 enhances accessibility through Reka UI integration. This provides automatic ARIA attributes, keyboard navigation support, intelligent focus management, and screen reader announcements. While offering a strong foundation, proper implementation and testing in your specific use case remains crucial for full accessibility compliance. For more detailed information, refer to [Reka UI's accessibility documentation](https://reka-ui.com/docs/overview/accessibility).
|
||||
::accordion-item{label="How does Nuxt UI handle accessibility?"}
|
||||
Nuxt UI enhances accessibility through Reka UI integration. This provides automatic ARIA attributes, keyboard navigation support, intelligent focus management, and screen reader announcements. While offering a strong foundation, proper implementation and testing in your specific use case remains crucial for full accessibility compliance. For more detailed information, refer to [Reka UI's accessibility documentation](https://reka-ui.com/docs/overview/accessibility).
|
||||
::
|
||||
|
||||
::accordion-item{label="What is the testing approach for Nuxt UI v3?"}
|
||||
Nuxt UI v3 ensures reliability with 1000+ Vitest tests, covering core functionality and accessibility. This robust testing suite supports the library's stability and serves as a reference for developers.
|
||||
::
|
||||
|
||||
::accordion-item{label="Is this version stable and suitable for production use?"}
|
||||
Nuxt UI v3 is now in beta and is stable enough to be used in production. We now recommend using v3 over v2. We welcome feedback from users to help improve the library further. Feel free to report any issues you encounter on our [GitHub repository](https://github.com/nuxt/ui/issues).
|
||||
::accordion-item{label="What is the testing approach for Nuxt UI?"}
|
||||
Nuxt UI ensures reliability with 1000+ Vitest tests, covering core functionality and accessibility. This robust testing suite supports the library's stability and serves as a reference for developers.
|
||||
::
|
||||
::
|
||||
|
||||
:hr
|
||||
|
||||
We're excited about the possibilities Nuxt UI v3 brings to your projects. Explore our documentation to learn more about new features, components, and best practices for building powerful, accessible user interfaces with Nuxt UI v3.
|
||||
We're excited about the possibilities Nuxt UI v3 brings to your projects. Explore our documentation to learn more about new features, components, and best practices for building powerful, accessible user interfaces.
|
||||
|
||||
@@ -20,24 +20,24 @@ Looking for the **Vue** version?
|
||||
|
||||
::steps{level="4"}
|
||||
|
||||
#### Install the Nuxt UI v3 beta package
|
||||
#### Install the Nuxt UI package
|
||||
|
||||
::code-group{sync="pm"}
|
||||
|
||||
```bash [pnpm]
|
||||
pnpm add @nuxt/ui@next
|
||||
pnpm add @nuxt/ui
|
||||
```
|
||||
|
||||
```bash [yarn]
|
||||
yarn add @nuxt/ui@next
|
||||
yarn add @nuxt/ui
|
||||
```
|
||||
|
||||
```bash [npm]
|
||||
npm install @nuxt/ui@next
|
||||
npm install @nuxt/ui
|
||||
```
|
||||
|
||||
```bash [bun]
|
||||
bun add @nuxt/ui@next
|
||||
bun add @nuxt/ui
|
||||
```
|
||||
|
||||
::
|
||||
@@ -85,7 +85,11 @@ It's recommended to install the [Tailwind CSS IntelliSense](https://marketplace.
|
||||
},
|
||||
"editor.quickSuggestions": {
|
||||
"strings": "on"
|
||||
}
|
||||
},
|
||||
"tailwindCSS.classAttributes": ["class", "ui"],
|
||||
"tailwindCSS.experimental.classRegex": [
|
||||
["ui:\\s*{([^)]*)\\s*}", "(?:'|\"|`)([^']*)(?:'|\"|`)"]
|
||||
]
|
||||
```
|
||||
|
||||
::
|
||||
@@ -108,12 +112,12 @@ The `App` component provides global configurations and is required for **Toast**
|
||||
|
||||
### Use our Nuxt Starter
|
||||
|
||||
Start your project using the [nuxt/starter#ui3](https://github.com/nuxt/starter/tree/ui3) template with Nuxt UI v3 pre-configured.
|
||||
Start your project using the [nuxt/starter#ui](https://github.com/nuxt/starter/tree/ui) template with Nuxt UI pre-configured.
|
||||
|
||||
Create a new project locally by running the following command:
|
||||
|
||||
```bash [Terminal]
|
||||
npx nuxi init -t ui3 <my-app>
|
||||
npx nuxi init -t ui <my-app>
|
||||
```
|
||||
|
||||
::note
|
||||
@@ -225,30 +229,19 @@ This option adds the `transition-colors` class on components with hover or activ
|
||||
|
||||
## Continuous Releases
|
||||
|
||||
Nuxt UI v3 uses [pkg.pr.new](https://github.com/stackblitz-labs/pkg.pr.new) for continuous preview releases, providing developers with instant access to the latest features and bug fixes without waiting for official releases.
|
||||
Nuxt UI uses [pkg.pr.new](https://github.com/stackblitz-labs/pkg.pr.new) for continuous preview releases, providing developers with instant access to the latest features and bug fixes without waiting for official releases.
|
||||
|
||||
Preview releases are automatically generated for every commit to the `v3` branch and pull requests targeting the `v3` branch. To use it into your project, use the installation command below by replacing `5385f84` with any commit hash or pull request number.
|
||||
Automatic preview releases are created for all commits and PRs to the `v3` branch. Use them by replacing your package version with the specific commit hash or PR number.
|
||||
|
||||
::code-group{sync="pm"}
|
||||
|
||||
```bash [pnpm]
|
||||
pnpm add https://pkg.pr.new/@nuxt/ui@5385f84
|
||||
```diff [package.json]
|
||||
{
|
||||
"dependencies": {
|
||||
- "@nuxt/ui": "^3.0.0",
|
||||
+ "@nuxt/ui": "https://pkg.pr.new/@nuxt/ui@4c96909",
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```bash [yarn]
|
||||
yarn add https://pkg.pr.new/@nuxt/ui@5385f84
|
||||
```
|
||||
|
||||
```bash [npm]
|
||||
npm install https://pkg.pr.new/@nuxt/ui@5385f84
|
||||
```
|
||||
|
||||
```bash [bun]
|
||||
bun add https://pkg.pr.new/@nuxt/ui@5385f84
|
||||
```
|
||||
|
||||
::
|
||||
|
||||
::note
|
||||
**pkg.pr.new** will automatically comment on PRs with the installation URL, making it easy to test changes.
|
||||
::
|
||||
|
||||
@@ -20,24 +20,24 @@ Looking for the **Nuxt** version?
|
||||
|
||||
::steps{level="4"}
|
||||
|
||||
#### Install the Nuxt UI v3 beta package
|
||||
#### Install the Nuxt UI package
|
||||
|
||||
::code-group{sync="pm"}
|
||||
|
||||
```bash [pnpm]
|
||||
pnpm add @nuxt/ui@next
|
||||
pnpm add @nuxt/ui
|
||||
```
|
||||
|
||||
```bash [yarn]
|
||||
yarn add @nuxt/ui@next
|
||||
yarn add @nuxt/ui
|
||||
```
|
||||
|
||||
```bash [npm]
|
||||
npm install @nuxt/ui@next
|
||||
npm install @nuxt/ui
|
||||
```
|
||||
|
||||
```bash [bun]
|
||||
bun add @nuxt/ui@next
|
||||
bun add @nuxt/ui
|
||||
```
|
||||
|
||||
::
|
||||
@@ -145,7 +145,11 @@ It's recommended to install the [Tailwind CSS IntelliSense](https://marketplace.
|
||||
},
|
||||
"editor.quickSuggestions": {
|
||||
"strings": "on"
|
||||
}
|
||||
},
|
||||
"tailwindCSS.classAttributes": ["class", "ui"],
|
||||
"tailwindCSS.experimental.classRegex": [
|
||||
["ui:\\s*{([^)]*)\\s*}", "(?:'|\"|`)([^']*)(?:'|\"|`)"]
|
||||
]
|
||||
```
|
||||
|
||||
::
|
||||
@@ -168,7 +172,7 @@ The `App` component provides global configurations and is required for **Toast**
|
||||
|
||||
### Use our Vue starter
|
||||
|
||||
Start your project using the [nuxtlabs/nuxt-ui-vue-starter](https://github.com/nuxtlabs/nuxt-ui-vue-starter) template with Nuxt UI v3 pre-configured.
|
||||
Start your project using the [nuxtlabs/nuxt-ui-vue-starter](https://github.com/nuxtlabs/nuxt-ui-vue-starter) template with Nuxt UI pre-configured.
|
||||
|
||||
Create a new project locally by running the following command:
|
||||
|
||||
@@ -313,30 +317,19 @@ This option adds the `transition-colors` class on components with hover or activ
|
||||
|
||||
## Continuous Releases
|
||||
|
||||
Nuxt UI v3 uses [pkg.pr.new](https://github.com/stackblitz-labs/pkg.pr.new) for continuous preview releases, providing developers with instant access to the latest features and bug fixes without waiting for official releases.
|
||||
Nuxt UI uses [pkg.pr.new](https://github.com/stackblitz-labs/pkg.pr.new) for continuous preview releases, providing developers with instant access to the latest features and bug fixes without waiting for official releases.
|
||||
|
||||
Preview releases are automatically generated for every commit to the `v3` branch and pull requests targeting the `v3` branch. To use it into your project, use the installation command below by replacing `5385f84` with any commit hash or pull request number.
|
||||
Automatic preview releases are created for all commits and PRs to the `v3` branch. Use them by replacing your package version with the specific commit hash or PR number.
|
||||
|
||||
::code-group{sync="pm"}
|
||||
|
||||
```bash [pnpm]
|
||||
pnpm add https://pkg.pr.new/@nuxt/ui@5385f84
|
||||
```diff [package.json]
|
||||
{
|
||||
"dependencies": {
|
||||
- "@nuxt/ui": "^3.0.0",
|
||||
+ "@nuxt/ui": "https://pkg.pr.new/@nuxt/ui@4c96909",
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```bash [yarn]
|
||||
yarn add https://pkg.pr.new/@nuxt/ui@5385f84
|
||||
```
|
||||
|
||||
```bash [npm]
|
||||
npm install https://pkg.pr.new/@nuxt/ui@5385f84
|
||||
```
|
||||
|
||||
```bash [bun]
|
||||
bun add https://pkg.pr.new/@nuxt/ui@5385f84
|
||||
```
|
||||
|
||||
::
|
||||
|
||||
::note
|
||||
**pkg.pr.new** will automatically comment on PRs with the installation URL, making it easy to test changes.
|
||||
::
|
||||
|
||||
@@ -56,19 +56,19 @@ npx @tailwindcss/upgrade
|
||||
::::code-group{sync="pm"}
|
||||
|
||||
```bash [pnpm]
|
||||
pnpm add @nuxt/ui@next
|
||||
pnpm add @nuxt/ui
|
||||
```
|
||||
|
||||
```bash [yarn]
|
||||
yarn add @nuxt/ui@next
|
||||
yarn add @nuxt/ui
|
||||
```
|
||||
|
||||
```bash [npm]
|
||||
npm install @nuxt/ui@next
|
||||
npm install @nuxt/ui
|
||||
```
|
||||
|
||||
```bash [bun]
|
||||
bun add @nuxt/ui@next
|
||||
bun add @nuxt/ui
|
||||
```
|
||||
|
||||
::::
|
||||
@@ -81,19 +81,19 @@ bun add @nuxt/ui@next
|
||||
::::code-group{sync="pm"}
|
||||
|
||||
```bash [pnpm]
|
||||
pnpm add @nuxt/ui-pro@next
|
||||
pnpm add @nuxt/ui-pro
|
||||
```
|
||||
|
||||
```bash [yarn]
|
||||
yarn add @nuxt/ui-pro@next
|
||||
yarn add @nuxt/ui-pro
|
||||
```
|
||||
|
||||
```bash [npm]
|
||||
npm install @nuxt/ui-pro@next
|
||||
npm install @nuxt/ui-pro
|
||||
```
|
||||
|
||||
```bash [bun]
|
||||
bun add @nuxt/ui-pro@next
|
||||
bun add @nuxt/ui-pro
|
||||
```
|
||||
|
||||
::::
|
||||
@@ -130,7 +130,7 @@ bun add @nuxt/ui-pro@next
|
||||
#ui
|
||||
|
||||
:::div
|
||||
5. Wrap you app with the [App](/components/app) component:
|
||||
5. Wrap your app with the [App](/components/app) component:
|
||||
:::
|
||||
|
||||
#ui-pro
|
||||
@@ -145,7 +145,7 @@ export default defineNuxtConfig({
|
||||
})
|
||||
```
|
||||
|
||||
6. Wrap you app with the [App](/components/app) component:
|
||||
6. Wrap your app with the [App](/components/app) component:
|
||||
:::
|
||||
|
||||
::
|
||||
@@ -359,7 +359,7 @@ In addition to the renamed components, there are lots of changes to the componen
|
||||
+ <USelect :items="countries" />
|
||||
|
||||
- <UHorizontalNavigation :links="links" />
|
||||
+ <UNavigationMenu :items="items" />
|
||||
+ <UNavigationMenu :items="links" />
|
||||
</template>
|
||||
```
|
||||
|
||||
@@ -367,6 +367,165 @@ In addition to the renamed components, there are lots of changes to the componen
|
||||
This change affects the following components: `Breadcrumb`, `HorizontalNavigation`, `InputMenu`, `RadioGroup`, `Select`, `SelectMenu`, `VerticalNavigation`.
|
||||
::
|
||||
|
||||
2. The global `Modals`, `Slideovers` and `Notifications` components have been removed in favor the [App](/components/app) component:
|
||||
|
||||
```diff [app.vue]
|
||||
<template>
|
||||
+ <UApp>
|
||||
+ <NuxtPage />
|
||||
+ </UApp>
|
||||
- <UModals />
|
||||
- <USlideovers />
|
||||
- <UNotifications />
|
||||
</template>
|
||||
```
|
||||
|
||||
3. The `v-model:open` directive and `default-open` prop are now used to control visibility:
|
||||
|
||||
```diff
|
||||
<template>
|
||||
- <UModal v-model="open" />
|
||||
+ <UModal v-model:open="open" />
|
||||
</template>
|
||||
```
|
||||
|
||||
::note
|
||||
This change affects the following components: `ContextMenu`, `Modal` and `Slideover` and enables controlling visibility for `InputMenu`, `Select`, `SelectMenu` and `Tooltip`.
|
||||
::
|
||||
|
||||
4. The default slot is now used for the trigger and the content goes inside the `#content` slot (you don't need to use a `v-model:open` directive with this method):
|
||||
|
||||
```diff
|
||||
<script setup lang="ts">
|
||||
- const open = ref(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
- <UButton label="Open" @click="open = true" />
|
||||
|
||||
- <UModal v-model="open">
|
||||
+ <UModal>
|
||||
+ <UButton label="Open" />
|
||||
|
||||
+ <template #content>
|
||||
<div class="p-4">
|
||||
<Placeholder class="h-48" />
|
||||
</div>
|
||||
+ </template>
|
||||
</UModal>
|
||||
</template>
|
||||
```
|
||||
|
||||
::note
|
||||
This change affects the following components: `Modal`, `Popover`, `Slideover`, `Tooltip`.
|
||||
::
|
||||
|
||||
5. A `#header`, `#body` and `#footer` slots have been added inside the `#content` slot like the `Card` component:
|
||||
|
||||
```diff
|
||||
<template>
|
||||
- <UModal>
|
||||
+ <UModal title="Title" description="Description">
|
||||
- <div class="p-4">
|
||||
+ <template #body>
|
||||
<Placeholder class="h-48" />
|
||||
+ </template>
|
||||
- </div>
|
||||
</UModal>
|
||||
</template>
|
||||
```
|
||||
|
||||
::note
|
||||
This change affects the following components: `Modal`, `Slideover`.
|
||||
::
|
||||
|
||||
|
||||
### Changed composables
|
||||
|
||||
1. The `useToast()` composable `timeout` prop has been renamed to `duration`:
|
||||
|
||||
```diff
|
||||
<script setup lang="ts">
|
||||
const toast = useToast()
|
||||
|
||||
- toast.add({ title: 'Invitation sent', timeout: 0 })
|
||||
+ toast.add({ title: 'Invitation sent', duration: 0 })
|
||||
</script>
|
||||
```
|
||||
|
||||
2. The `useModal` and `useSlideover` composables have been removed in favor of a more generic `useOverlay` composable:
|
||||
|
||||
Some important differences:
|
||||
- The `useOverlay` composable is now used to create overlay instances
|
||||
- Overlays that are opened, can be awaited for their result
|
||||
- Overlays can no longer be close using `modal.close()` or `slideover.close()`, rather, they close automatically: either when a `close` event is fired explicitly from the opened component OR when the overlay closes itself (clicking on backdrop, pressing the ESC key, etc)
|
||||
- To capture the return value in the parent component you must explictly emit a `close` event with the desired value
|
||||
|
||||
|
||||
```diff
|
||||
<script setup lang="ts">
|
||||
import { ModalExampleComponent } from '#components'
|
||||
|
||||
- const modal = useModal()
|
||||
+ const overlay = useOverlay()
|
||||
|
||||
- modal.open(ModalExampleComponent)
|
||||
+ const modal = overlay.create(ModalExampleComponent)
|
||||
</script>
|
||||
```
|
||||
|
||||
Props are now passed through a props attribute:
|
||||
|
||||
```diff
|
||||
<script setup lang="ts">
|
||||
import { ModalExampleComponent } from '#components'
|
||||
|
||||
- const modal = useModal()
|
||||
+ const overlay = useOverlay()
|
||||
|
||||
const count = ref(0)
|
||||
|
||||
- modal.open(ModalExampleComponent, {
|
||||
- count: count.value
|
||||
- })
|
||||
+ const modal = overlay.create(ModalExampleComponent, {
|
||||
+ props: {
|
||||
+ count: count.value
|
||||
+ }
|
||||
+ })
|
||||
</script>
|
||||
```
|
||||
|
||||
Closing a modal is now done through the `close` event. The `modal.open` method now returns a promise that resolves to the result of the modal whenever the modal is close:
|
||||
|
||||
```diff
|
||||
<script setup lang="ts">
|
||||
import { ModalExampleComponent } from '#components'
|
||||
|
||||
- const modal = useModal()
|
||||
+ const overlay = useOverlay()
|
||||
|
||||
+ const modal = overlay.create(ModalExampleComponent)
|
||||
|
||||
- function openModal() {
|
||||
- modal.open(ModalExampleComponent, {
|
||||
- onSuccess() {
|
||||
- toast.add({ title: 'Success!' })
|
||||
- }
|
||||
- })
|
||||
- }
|
||||
+ async function openModal() {
|
||||
+ const result = await modal.open(ModalExampleComponent, {
|
||||
+ count: count.value
|
||||
+ })
|
||||
+
|
||||
+ if (result) {
|
||||
+ toast.add({ title: 'Success!' })
|
||||
+ }
|
||||
+ }
|
||||
</script>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
::warning
|
||||
|
||||
@@ -6,7 +6,7 @@ navigation.icon: i-lucide-swatch-book
|
||||
|
||||
## Tailwind CSS
|
||||
|
||||
Nuxt UI v3 uses Tailwind CSS v4, you can read the official [upgrade guide](https://tailwindcss.com/docs/upgrade-guide#changes-from-v3) to learn about all the breaking changes.
|
||||
Nuxt UI uses Tailwind CSS v4, you can read the official [upgrade guide](https://tailwindcss.com/docs/upgrade-guide#changes-from-v3) to learn about all the breaking changes.
|
||||
|
||||
### `@theme`
|
||||
|
||||
@@ -142,7 +142,7 @@ Nuxt UI leverages Vite config to provide customizable color aliases based on [Ta
|
||||
|
||||
::framework-only
|
||||
#nuxt
|
||||
::div
|
||||
:::div
|
||||
You can configure these color aliases at runtime in your `app.config.ts` file under the `ui.colors` key, allowing for dynamic theme customization without requiring an application rebuild:
|
||||
|
||||
```ts [app.config.ts]
|
||||
@@ -156,14 +156,19 @@ export default defineAppConfig({
|
||||
})
|
||||
```
|
||||
|
||||
::
|
||||
:::
|
||||
|
||||
#vue
|
||||
::module-only
|
||||
#ui
|
||||
|
||||
:::div
|
||||
You can configure these color aliases at runtime in your `vite.config.ts` file under the `ui.colors` key:
|
||||
|
||||
::::module-only
|
||||
|
||||
#ui
|
||||
|
||||
:::::div
|
||||
|
||||
```ts [vite.config.ts]
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
@@ -183,11 +188,12 @@ export default defineConfig({
|
||||
]
|
||||
})
|
||||
```
|
||||
:::
|
||||
:::::
|
||||
|
||||
#ui-pro
|
||||
:::div
|
||||
You can configure these color aliases at runtime in your `vite.config.ts` file under the `uiPro.colors` key:
|
||||
|
||||
:::::div
|
||||
|
||||
```ts [vite.config.ts]
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
@@ -207,9 +213,12 @@ export default defineConfig({
|
||||
]
|
||||
})
|
||||
```
|
||||
:::
|
||||
|
||||
::
|
||||
:::::
|
||||
|
||||
::::
|
||||
|
||||
:::
|
||||
|
||||
::
|
||||
|
||||
@@ -256,11 +265,17 @@ export default defineNuxtConfig({
|
||||
:::
|
||||
|
||||
#vue
|
||||
::module-only
|
||||
#ui
|
||||
|
||||
:::tip
|
||||
|
||||
You can add you own dynamic color aliases in your `vite.config.ts`, you just have to make sure to also define them in the [`theme.colors`](/getting-started/installation/vue#themecolors) option of the `ui` plugin.
|
||||
|
||||
::::module-only
|
||||
|
||||
#ui
|
||||
|
||||
:::::div
|
||||
|
||||
```ts [vite.config.ts]
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
@@ -283,11 +298,11 @@ export default defineConfig({
|
||||
})
|
||||
```
|
||||
|
||||
:::
|
||||
:::::
|
||||
|
||||
#ui-pro
|
||||
:::tip
|
||||
You can add you own dynamic color aliases in your `vite.config.ts`, you just have to make sure to also define them in the [`theme.colors`](/getting-started/installation/vue#themecolors) option of the `uiPro` plugin.
|
||||
|
||||
:::::div
|
||||
|
||||
```ts [vite.config.ts]
|
||||
import { defineConfig } from 'vite'
|
||||
@@ -311,9 +326,12 @@ export default defineConfig({
|
||||
})
|
||||
```
|
||||
|
||||
:::::
|
||||
|
||||
::::
|
||||
|
||||
:::
|
||||
|
||||
::
|
||||
::
|
||||
|
||||
### Tokens
|
||||
|
||||
@@ -17,6 +17,11 @@ Nuxt UI provides an **App** component that wraps your app to provide global conf
|
||||
|
||||
### Locale
|
||||
|
||||
::module-only
|
||||
|
||||
#ui
|
||||
:::div
|
||||
|
||||
Use the `locale` prop with the locale you want to use from `@nuxt/ui/locale`:
|
||||
|
||||
```vue [app.vue]
|
||||
@@ -31,13 +36,42 @@ import { fr } from '@nuxt/ui/locale'
|
||||
</template>
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
#ui-pro
|
||||
:::div
|
||||
|
||||
Use the `locale` prop with the locale you want to use from `@nuxt/ui-pro/locale`:
|
||||
|
||||
```vue [app.vue]
|
||||
<script setup lang="ts">
|
||||
import { fr } from '@nuxt/ui-pro/locale'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UApp :locale="fr">
|
||||
<NuxtPage />
|
||||
</UApp>
|
||||
</template>
|
||||
```
|
||||
|
||||
:::
|
||||
::
|
||||
|
||||
### Custom locale
|
||||
|
||||
You also have the option to add your own locale using `defineLocale`:
|
||||
|
||||
::module-only
|
||||
|
||||
#ui
|
||||
:::div
|
||||
|
||||
```vue [app.vue]
|
||||
<script setup lang="ts">
|
||||
const locale = defineLocale({
|
||||
import type { Messages } from '@nuxt/ui'
|
||||
|
||||
const locale = defineLocale<Messages>({
|
||||
name: 'My custom locale',
|
||||
code: 'en',
|
||||
dir: 'ltr',
|
||||
@@ -54,6 +88,35 @@ const locale = defineLocale({
|
||||
</template>
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
#ui-pro
|
||||
:::div
|
||||
|
||||
```vue [app.vue]
|
||||
<script setup lang="ts">
|
||||
import type { Messages } from '@nuxt/ui-pro'
|
||||
|
||||
const locale = defineLocale<Messages>({
|
||||
name: 'My custom locale',
|
||||
code: 'en',
|
||||
dir: 'ltr',
|
||||
messages: {
|
||||
// implement pairs
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UApp :locale="locale">
|
||||
<NuxtPage />
|
||||
</UApp>
|
||||
</template>
|
||||
```
|
||||
|
||||
:::
|
||||
::
|
||||
|
||||
::tip
|
||||
Look at the `code` parameter, there you need to pass the iso code of the language. Example:
|
||||
|
||||
@@ -116,6 +179,11 @@ export default defineNuxtConfig({
|
||||
|
||||
#### Set the `locale` prop using `useI18n`
|
||||
|
||||
::module-only
|
||||
|
||||
#ui
|
||||
:::div
|
||||
|
||||
```vue [app.vue]
|
||||
<script setup lang="ts">
|
||||
import * as locales from '@nuxt/ui/locale'
|
||||
@@ -130,6 +198,28 @@ const { locale } = useI18n()
|
||||
</template>
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
#ui-pro
|
||||
:::div
|
||||
|
||||
```vue [app.vue]
|
||||
<script setup lang="ts">
|
||||
import * as locales from '@nuxt/ui-pro/locale'
|
||||
|
||||
const { locale } = useI18n()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UApp :locale="locales[locale]">
|
||||
<NuxtPage />
|
||||
</UApp>
|
||||
</template>
|
||||
```
|
||||
|
||||
:::
|
||||
::
|
||||
|
||||
::
|
||||
|
||||
### Dynamic direction
|
||||
@@ -138,6 +228,11 @@ Each locale has a `dir` property which will be used by the `App` component to se
|
||||
|
||||
In a multilingual application, you might want to set the `lang` and `dir` attributes on the `<html>` element dynamically based on the user's locale, which you can do with the [useHead](https://nuxt.com/docs/api/composables/use-head) composable:
|
||||
|
||||
::module-only
|
||||
|
||||
#ui
|
||||
:::div
|
||||
|
||||
```vue [app.vue]
|
||||
<script setup lang="ts">
|
||||
import * as locales from '@nuxt/ui/locale'
|
||||
@@ -162,6 +257,38 @@ useHead({
|
||||
</template>
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
#ui-pro
|
||||
:::div
|
||||
|
||||
```vue [app.vue]
|
||||
<script setup lang="ts">
|
||||
import * as locales from '@nuxt/ui-pro/locale'
|
||||
|
||||
const { locale } = useI18n()
|
||||
|
||||
const lang = computed(() => locales[locale.value].code)
|
||||
const dir = computed(() => locales[locale.value].dir)
|
||||
|
||||
useHead({
|
||||
htmlAttrs: {
|
||||
lang,
|
||||
dir
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UApp :locale="locales[locale]">
|
||||
<NuxtPage />
|
||||
</UApp>
|
||||
</template>
|
||||
```
|
||||
|
||||
:::
|
||||
::
|
||||
|
||||
## Supported languages
|
||||
|
||||
:supported-languages
|
||||
|
||||
@@ -17,6 +17,11 @@ Nuxt UI provides an **App** component that wraps your app to provide global conf
|
||||
|
||||
### Locale
|
||||
|
||||
::module-only
|
||||
|
||||
#ui
|
||||
:::div
|
||||
|
||||
Use the `locale` prop with the locale you want to use from `@nuxt/ui/locale`:
|
||||
|
||||
```vue [App.vue]
|
||||
@@ -31,15 +36,43 @@ import { fr } from '@nuxt/ui/locale'
|
||||
</template>
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
#ui-pro
|
||||
:::div
|
||||
|
||||
Use the `locale` prop with the locale you want to use from `@nuxt/ui-pro/locale`:
|
||||
|
||||
```vue [App.vue]
|
||||
<script setup lang="ts">
|
||||
import { fr } from '@nuxt/ui-pro/locale'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UApp :locale="fr">
|
||||
<RouterView />
|
||||
</UApp>
|
||||
</template>
|
||||
```
|
||||
|
||||
:::
|
||||
::
|
||||
|
||||
### Custom locale
|
||||
|
||||
You also have the option to add your locale using `defineLocale`:
|
||||
|
||||
::module-only
|
||||
|
||||
#ui
|
||||
:::div
|
||||
|
||||
```vue [App.vue]
|
||||
<script setup lang="ts">
|
||||
import { defineLocale } from '@nuxt/ui/composables/defineLocale'
|
||||
import type { Messages } from '@nuxt/ui'
|
||||
import { defineLocale } from '@nuxt/ui/composables/defineLocale.js'
|
||||
|
||||
const locale = defineLocale({
|
||||
const locale = defineLocale<Messages>({
|
||||
name: 'My custom locale',
|
||||
code: 'en',
|
||||
dir: 'ltr',
|
||||
@@ -56,6 +89,36 @@ const locale = defineLocale({
|
||||
</template>
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
#ui-pro
|
||||
:::div
|
||||
|
||||
```vue [App.vue]
|
||||
<script setup lang="ts">
|
||||
import type { Messages } from '@nuxt/ui-pro'
|
||||
import { defineLocale } from '@nuxt/ui/composables/defineLocale.js'
|
||||
|
||||
const locale = defineLocale<Messages>({
|
||||
name: 'My custom locale',
|
||||
code: 'en',
|
||||
dir: 'ltr',
|
||||
messages: {
|
||||
// implement pairs
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UApp :locale="locale">
|
||||
<RouterView />
|
||||
</UApp>
|
||||
</template>
|
||||
```
|
||||
|
||||
:::
|
||||
::
|
||||
|
||||
::tip
|
||||
Look at the `code` parameter, there you need to pass the iso code of the language. Example:
|
||||
|
||||
@@ -131,6 +194,11 @@ app.mount('#app')
|
||||
|
||||
#### Set the `locale` prop using `useI18n`
|
||||
|
||||
::module-only
|
||||
|
||||
#ui
|
||||
:::div
|
||||
|
||||
```vue [App.vue]
|
||||
<script setup lang="ts">
|
||||
import { useI18n } from 'vue-i18n'
|
||||
@@ -146,6 +214,29 @@ const { locale } = useI18n()
|
||||
</template>
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
#ui-pro
|
||||
:::div
|
||||
|
||||
```vue [App.vue]
|
||||
<script setup lang="ts">
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import * as locales from '@nuxt/ui-pro/locale'
|
||||
|
||||
const { locale } = useI18n()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UApp :locale="locales[locale]">
|
||||
<RouterView />
|
||||
</UApp>
|
||||
</template>
|
||||
```
|
||||
|
||||
:::
|
||||
::
|
||||
|
||||
::
|
||||
|
||||
### Dynamic direction
|
||||
@@ -154,6 +245,11 @@ Each locale has a `dir` property which will be used by the `App` component to se
|
||||
|
||||
In a multilingual application, you might want to set the `lang` and `dir` attributes on the `<html>` element dynamically based on the user's locale, which you can do with the [useHead](https://unhead.unjs.io/usage/composables/use-head) composable:
|
||||
|
||||
::module-only
|
||||
|
||||
#ui
|
||||
:::div
|
||||
|
||||
```vue [App.vue]
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
@@ -181,6 +277,41 @@ useHead({
|
||||
</template>
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
#ui-pro
|
||||
:::div
|
||||
|
||||
```vue [App.vue]
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useHead } from '@unhead/vue'
|
||||
import * as locales from '@nuxt/ui-pro/locale'
|
||||
|
||||
const { locale } = useI18n()
|
||||
|
||||
const lang = computed(() => locales[locale.value].code)
|
||||
const dir = computed(() => locales[locale.value].dir)
|
||||
|
||||
useHead({
|
||||
htmlAttrs: {
|
||||
lang,
|
||||
dir
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UApp :locale="locales[locale]">
|
||||
<RouterView />
|
||||
</UApp>
|
||||
</template>
|
||||
```
|
||||
|
||||
:::
|
||||
::
|
||||
|
||||
## Supported languages
|
||||
|
||||
:supported-languages
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
---
|
||||
title: Contribution Guide
|
||||
description: 'A comprehensive guide on contributing to Nuxt UI v3, including project structure, development workflow, and best practices.'
|
||||
description: 'A comprehensive guide on contributing to Nuxt UI, including project structure, development workflow, and best practices.'
|
||||
navigation: false
|
||||
---
|
||||
|
||||
Nuxt UI thrives thanks to its incredible community ❤️. We welcome all contributions through bug reports, pull requests, and feedback to help make this library even better.
|
||||
|
||||
::caution
|
||||
Before reporting a bug or requesting a feature, make sure that you have read through our [documentation](https://ui3.nuxt.dev/) and existing [issues](https://github.com/nuxt/ui/issues?q=is%3Aissue%20is%3Aopen%20sort%3Aupdated-desc%20label%3Av3).
|
||||
Before reporting a bug or requesting a feature, make sure that you have read through our [documentation](https://ui.nuxt.com/) and existing [issues](https://github.com/nuxt/ui/issues?q=is%3Aissue%20is%3Aopen%20sort%3Aupdated-desc%20label%3Av3).
|
||||
::
|
||||
|
||||
## Project Structure
|
||||
|
||||
@@ -29,23 +29,6 @@ props:
|
||||
---
|
||||
::
|
||||
|
||||
### Link
|
||||
|
||||
You can pass any property from the [Link](/components/link#props) component such as `to`, `target`, etc.
|
||||
|
||||
::component-code
|
||||
---
|
||||
ignore:
|
||||
- label
|
||||
- target
|
||||
props:
|
||||
to: https://github.com/nuxt/ui
|
||||
target: _blank
|
||||
slots:
|
||||
default: Button
|
||||
---
|
||||
::
|
||||
|
||||
### Color
|
||||
|
||||
Use the `color` prop to change the color of the Button.
|
||||
@@ -160,6 +143,96 @@ props:
|
||||
---
|
||||
::
|
||||
|
||||
### Link
|
||||
|
||||
You can pass any property from the [Link](/components/link#props) component such as `to`, `target`, etc.
|
||||
|
||||
::component-code
|
||||
---
|
||||
ignore:
|
||||
- target
|
||||
props:
|
||||
to: https://github.com/nuxt/ui
|
||||
target: _blank
|
||||
slots:
|
||||
default: Button
|
||||
---
|
||||
::
|
||||
|
||||
When the Button is a link or when using the `active` prop, you can use the `active-color` and `active-variant` props to customize the active state.
|
||||
|
||||
::component-code
|
||||
---
|
||||
prettier: true
|
||||
ignore:
|
||||
- color
|
||||
- variant
|
||||
items:
|
||||
activeColor:
|
||||
- primary
|
||||
- secondary
|
||||
- success
|
||||
- info
|
||||
- warning
|
||||
- error
|
||||
- neutral
|
||||
activeVariant:
|
||||
- solid
|
||||
- outline
|
||||
- soft
|
||||
- subtle
|
||||
- ghost
|
||||
- link
|
||||
props:
|
||||
active: true
|
||||
color: neutral
|
||||
variant: outline
|
||||
activeColor: primary
|
||||
activeVariant: solid
|
||||
slots:
|
||||
default: |
|
||||
|
||||
Button
|
||||
---
|
||||
|
||||
Button
|
||||
::
|
||||
|
||||
You can also use the `active-class` and `inactive-class` props to customize the active state.
|
||||
|
||||
::component-code
|
||||
---
|
||||
props:
|
||||
active: true
|
||||
activeClass: 'font-bold'
|
||||
inactiveClass: 'font-light'
|
||||
slots:
|
||||
default: Button
|
||||
---
|
||||
|
||||
Button
|
||||
::
|
||||
|
||||
::tip
|
||||
You can configure these styles globally in your `app.config.ts` file under the `ui.button.variants.active` key.
|
||||
|
||||
```ts
|
||||
export default defineAppConfig({
|
||||
ui: {
|
||||
button: {
|
||||
variants: {
|
||||
active: {
|
||||
true: {
|
||||
base: 'font-bold'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
::
|
||||
|
||||
### Loading
|
||||
|
||||
Use the `loading` prop to show a loading icon and disable the Button.
|
||||
|
||||
@@ -9,7 +9,7 @@ links:
|
||||
|
||||
## Usage
|
||||
|
||||
Use the Form component to validate form data using schema libraries such as [Valibot](https://github.com/fabian-hiller/valibot), [Zod](https://github.com/colinhacks/zod), [Yup](https://github.com/jquense/yup), [Joi](https://github.com/hapijs/joi), [Superstruct](https://github.com/ianstormtaylor/superstruct) or your own validation logic.
|
||||
Use the Form component to validate form data using validation libraries such as [Valibot](https://github.com/fabian-hiller/valibot), [Zod](https://github.com/colinhacks/zod), [Yup](https://github.com/jquense/yup), [Joi](https://github.com/hapijs/joi), [Superstruct](https://github.com/ianstormtaylor/superstruct) or your own validation logic.
|
||||
|
||||
It works with the [FormField](/components/form-field) component to display error messages around form elements automatically.
|
||||
|
||||
@@ -18,7 +18,7 @@ It works with the [FormField](/components/form-field) component to display error
|
||||
It requires two props:
|
||||
|
||||
- `state` - a reactive object holding the form's state.
|
||||
- `schema` - a schema object from a validation library like [Valibot](https://github.com/fabian-hiller/valibot), [Zod](https://github.com/colinhacks/zod), [Yup](https://github.com/jquense/yup), [Joi](https://github.com/hapijs/joi) or [Superstruct](https://github.com/ianstormtaylor/superstruct).
|
||||
- `schema` - any [Standard Schema](https://standardschema.dev/) or a schema from [Yup](https://github.com/jquense/yup), [Joi](https://github.com/hapijs/joi) or [Superstruct](https://github.com/ianstormtaylor/superstruct).
|
||||
|
||||
::warning
|
||||
**No validation library is included** by default, ensure you **install the one you need**.
|
||||
|
||||
@@ -8,7 +8,6 @@ links:
|
||||
- label: GitHub
|
||||
icon: i-simple-icons-github
|
||||
to: https://github.com/nuxt/ui/tree/v3/src/runtime/components/Tree.vue
|
||||
navigation.badge: New
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -34,7 +34,7 @@ export default defineNuxtConfig({
|
||||
},
|
||||
$production: {
|
||||
site: {
|
||||
url: 'https://ui3.nuxt.dev'
|
||||
url: 'https://ui.nuxt.com'
|
||||
}
|
||||
},
|
||||
|
||||
@@ -55,6 +55,7 @@ export default defineNuxtConfig({
|
||||
}]
|
||||
},
|
||||
rootAttrs: {
|
||||
// @ts-expect-error - vaul-drawer-wrapper is not typed
|
||||
'vaul-drawer-wrapper': '',
|
||||
'class': 'bg-(--ui-bg)'
|
||||
}
|
||||
@@ -85,8 +86,8 @@ export default defineNuxtConfig({
|
||||
},
|
||||
|
||||
routeRules: {
|
||||
'/': { redirect: '/getting-started', prerender: false },
|
||||
'/getting-started/installation': { redirect: '/getting-started/installation/nuxt', prerender: false },
|
||||
'/getting-started/installation/pro': { redirect: '/getting-started/installation/pro/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 },
|
||||
'/getting-started/i18n': { redirect: '/getting-started/i18n/nuxt', prerender: false },
|
||||
@@ -104,9 +105,10 @@ export default defineNuxtConfig({
|
||||
routes: [
|
||||
'/getting-started',
|
||||
'/api/countries.json',
|
||||
'/api/locales.json'
|
||||
'/api/locales.json',
|
||||
// '/api/releases.json',
|
||||
// '/api/pulls.json'
|
||||
'/404.html'
|
||||
],
|
||||
crawlLinks: true,
|
||||
autoSubfolderIndex: false
|
||||
@@ -127,7 +129,12 @@ export default defineNuxtConfig({
|
||||
vite: {
|
||||
plugins: [
|
||||
yaml()
|
||||
]
|
||||
],
|
||||
server: {
|
||||
fs: {
|
||||
allow: process.env.NUXT_UI_PRO_PATH ? [resolve(process.env.NUXT_UI_PRO_PATH)] : undefined
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
componentMeta: {
|
||||
@@ -169,12 +176,12 @@ export default defineNuxtConfig({
|
||||
},
|
||||
|
||||
llms: {
|
||||
domain: 'https://ui3.nuxt.dev',
|
||||
title: 'Nuxt UI v3',
|
||||
domain: 'https://ui.nuxt.com',
|
||||
title: 'Nuxt UI',
|
||||
description: 'A comprehensive, Nuxt-integrated UI library providing a rich set of fully-styled, accessible and highly customizable components for building modern web applications.',
|
||||
full: {
|
||||
title: 'Nuxt UI v3 Full Documentation',
|
||||
description: 'This is the full documentation for Nuxt UI v3. It includes all the Markdown files written with the MDC syntax.'
|
||||
title: 'Nuxt UI Full Documentation',
|
||||
description: 'This is the full documentation for Nuxt UI. It includes all the Markdown files written with the MDC syntax.'
|
||||
},
|
||||
sections: [
|
||||
{
|
||||
|
||||
@@ -4,34 +4,34 @@
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@iconify-json/logos": "^1.2.4",
|
||||
"@iconify-json/lucide": "^1.2.28",
|
||||
"@iconify-json/simple-icons": "^1.2.27",
|
||||
"@iconify-json/lucide": "^1.2.30",
|
||||
"@iconify-json/simple-icons": "^1.2.28",
|
||||
"@iconify-json/vscode-icons": "^1.2.16",
|
||||
"@nuxt/content": "https://pkg.pr.new/@nuxt/content@819ab7f",
|
||||
"@nuxt/content": "https://pkg.pr.new/@nuxt/content@be99f3a",
|
||||
"@nuxt/image": "^1.9.0",
|
||||
"@nuxt/ui": "latest",
|
||||
"@nuxt/ui-pro": "https://pkg.pr.new/@nuxt/ui-pro@7cf4b69",
|
||||
"@nuxthub/core": "^0.8.17",
|
||||
"@nuxt/ui-pro": "https://pkg.pr.new/@nuxt/ui-pro@d96a086",
|
||||
"@nuxthub/core": "^0.8.18",
|
||||
"@nuxtjs/plausible": "^1.2.0",
|
||||
"@octokit/rest": "^21.1.1",
|
||||
"@rollup/plugin-yaml": "^4.1.2",
|
||||
"@vueuse/nuxt": "^12.7.0",
|
||||
"@vueuse/nuxt": "^13.0.0",
|
||||
"joi": "^17.13.3",
|
||||
"motion": "^12.4.7",
|
||||
"motion-v": "0.11.0-beta.6",
|
||||
"nuxt": "^3.15.4",
|
||||
"motion": "^12.5.0",
|
||||
"motion-v": "0.13.0",
|
||||
"nuxt": "^3.16.0",
|
||||
"nuxt-component-meta": "^0.10.0",
|
||||
"nuxt-llms": "^0.1.0",
|
||||
"nuxt-og-image": "^4.1.5",
|
||||
"nuxt-og-image": "^5.0.4",
|
||||
"prettier": "^3.5.3",
|
||||
"shiki-transformer-color-highlight": "^0.2.0",
|
||||
"shiki-transformer-color-highlight": "^1.0.0",
|
||||
"superstruct": "^2.0.2",
|
||||
"ufo": "^1.5.4",
|
||||
"valibot": "^0.42.1",
|
||||
"valibot": "^1.0.0",
|
||||
"yup": "^1.6.1",
|
||||
"zod": "^3.24.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"wrangler": "^3.111.0"
|
||||
"wrangler": "^3.114.0"
|
||||
}
|
||||
}
|
||||
|
||||
BIN
docs/public/pro/ad.png
Normal file
BIN
docs/public/pro/ad.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 86 KiB |
59
package.json
59
package.json
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"name": "@nuxt/ui",
|
||||
"description": "A UI Library for Modern Web Apps, powered by Vue & Tailwind CSS.",
|
||||
"version": "3.0.0-beta.2",
|
||||
"packageManager": "pnpm@10.5.2",
|
||||
"version": "3.0.0",
|
||||
"packageManager": "pnpm@10.6.4",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/nuxt/ui.git"
|
||||
},
|
||||
"homepage": "https://ui3.nuxt.dev",
|
||||
"homepage": "https://ui.nuxt.com",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"exports": {
|
||||
@@ -67,7 +67,7 @@
|
||||
"dev:build": "nuxi build playground",
|
||||
"dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxi prepare playground && nuxi prepare docs && vite build playground-vue",
|
||||
"docs": "DEV=true nuxi dev docs",
|
||||
"docs:build": "nuxi build docs",
|
||||
"docs:build": "NODE_OPTIONS='--max-old-space-size=8192' nuxi build docs",
|
||||
"docs:prepare": "nuxt-component-meta docs",
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "eslint . --fix",
|
||||
@@ -75,23 +75,23 @@
|
||||
"test": "vitest",
|
||||
"test:vue": "vitest -c vitest.vue.config.ts",
|
||||
"test:vue:build": "vite build playground-vue",
|
||||
"release": "release-it --preRelease=beta --npm.tag=next"
|
||||
"release": "release-it"
|
||||
},
|
||||
"dependencies": {
|
||||
"@iconify/vue": "^4.3.0",
|
||||
"@internationalized/date": "^3.7.0",
|
||||
"@internationalized/number": "^3.6.0",
|
||||
"@nuxt/fonts": "^0.10.3",
|
||||
"@nuxt/icon": "^1.10.3",
|
||||
"@nuxt/kit": "^3.15.4",
|
||||
"@nuxt/schema": "^3.15.4",
|
||||
"@nuxt/fonts": "^0.11.0",
|
||||
"@nuxt/icon": "^1.11.0",
|
||||
"@nuxt/kit": "^3.16.0",
|
||||
"@nuxt/schema": "^3.16.0",
|
||||
"@nuxtjs/color-mode": "^3.5.2",
|
||||
"@tailwindcss/postcss": "^4.0.9",
|
||||
"@tailwindcss/vite": "^4.0.9",
|
||||
"@tailwindcss/postcss": "^4.0.14",
|
||||
"@tailwindcss/vite": "^4.0.14",
|
||||
"@tanstack/vue-table": "^8.21.2",
|
||||
"@unhead/vue": "^1.11.20",
|
||||
"@vueuse/core": "^12.7.0",
|
||||
"@vueuse/integrations": "^12.7.0",
|
||||
"@unhead/vue": "^2.0.0-rc.13",
|
||||
"@vueuse/core": "^13.0.0",
|
||||
"@vueuse/integrations": "^13.0.0",
|
||||
"colortranslator": "^4.1.0",
|
||||
"consola": "^3.4.0",
|
||||
"defu": "^6.1.4",
|
||||
@@ -106,34 +106,36 @@
|
||||
"knitwork": "^1.2.0",
|
||||
"magic-string": "^0.30.17",
|
||||
"mlly": "^1.7.4",
|
||||
"ohash": "^1.1.5",
|
||||
"ohash": "^2.0.11",
|
||||
"pathe": "^2.0.3",
|
||||
"reka-ui": "^2.0.2",
|
||||
"reka-ui": "^2.1.0",
|
||||
"scule": "^1.3.0",
|
||||
"tailwind-variants": "^0.3.1",
|
||||
"tailwindcss": "^4.0.9",
|
||||
"tailwind-variants": "^1.0.0",
|
||||
"tailwindcss": "^4.0.14",
|
||||
"tinyglobby": "^0.2.12",
|
||||
"unplugin": "^2.2.0",
|
||||
"unplugin-auto-import": "^19.1.1",
|
||||
"unplugin-vue-components": "^28.4.1",
|
||||
"vaul-vue": "^0.3.0"
|
||||
"vaul-vue": "^0.3.0",
|
||||
"vue": "^3.5.13",
|
||||
"vue-router": "^4.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nuxt/eslint-config": "^1.1.0",
|
||||
"@nuxt/eslint-config": "^1.2.0",
|
||||
"@nuxt/module-builder": "^0.8.4",
|
||||
"@nuxt/test-utils": "^3.17.1",
|
||||
"@nuxt/test-utils": "^3.17.2",
|
||||
"@release-it/conventional-changelog": "^10.0.0",
|
||||
"@standard-schema/spec": "^1.0.0",
|
||||
"@vue/test-utils": "^2.4.6",
|
||||
"embla-carousel": "^8.5.2",
|
||||
"eslint": "^9.21.0",
|
||||
"happy-dom": "^17.1.2",
|
||||
"eslint": "^9.22.0",
|
||||
"happy-dom": "^17.4.4",
|
||||
"joi": "^17.13.3",
|
||||
"nuxt": "^3.15.4",
|
||||
"nuxt": "^3.16.0",
|
||||
"release-it": "^18.1.2",
|
||||
"superstruct": "^2.0.2",
|
||||
"valibot": "^0.42.1",
|
||||
"vitest": "^3.0.7",
|
||||
"valibot": "^1.0.0",
|
||||
"vitest": "^3.0.9",
|
||||
"vitest-environment-nuxt": "^1.0.1",
|
||||
"vue-tsc": "^2.2.0",
|
||||
"yup": "^1.6.1",
|
||||
@@ -146,12 +148,9 @@
|
||||
"@nuxt/ui": "workspace:*",
|
||||
"chokidar": "3.6.0",
|
||||
"debug": "4.3.7",
|
||||
"happy-dom": "17.1.2",
|
||||
"rollup": "4.32.1",
|
||||
"rollup": "4.34.9",
|
||||
"typescript": "5.6.3",
|
||||
"unimport": "3.14.5",
|
||||
"unplugin": "^2.2.0",
|
||||
"vue": "3.5.13",
|
||||
"vue-tsc": "2.2.0"
|
||||
},
|
||||
"pnpm": {
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/logo.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Nuxt UI ❤️ Vue</title>
|
||||
<title>Nuxt UI - Vue Playground</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app" class="isolate"></div>
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
"vue-router": "^4.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^5.2.1",
|
||||
"@vitejs/plugin-vue": "^5.2.3",
|
||||
"typescript": "^5.6.3",
|
||||
"vite": "^6.2.0",
|
||||
"vite": "^6.2.2",
|
||||
"vue-tsc": "^2.2.0"
|
||||
}
|
||||
}
|
||||
|
||||
8
playground-vue/public/logo.svg
Normal file
8
playground-vue/public/logo.svg
Normal file
@@ -0,0 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128">
|
||||
<style>
|
||||
.st0 { fill: #42B883; }
|
||||
.st1 { fill: #35495E; }
|
||||
</style>
|
||||
<path class="st0" d="M78.8,10L64,35.4L49.2,10H0l64,110l64-110C128,10,78.8,10,78.8,10z" />
|
||||
<path class="st1" d="M78.8,10L64,35.4L49.2,10H25.6L64,76l38.4-66H78.8z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 316 B |
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { h, resolveComponent } from 'vue'
|
||||
import { upperFirst } from 'scule'
|
||||
import type { TableColumn } from '@nuxt/ui'
|
||||
import type { TableColumn, TableRow } from '@nuxt/ui'
|
||||
import { getPaginationRowModel } from '@tanstack/vue-table'
|
||||
|
||||
const UButton = resolveComponent('UButton')
|
||||
@@ -279,6 +279,10 @@ function randomize() {
|
||||
data.value = [...data.value].sort(() => Math.random() - 0.5)
|
||||
}
|
||||
|
||||
function onSelect(row: TableRow<Payment>) {
|
||||
console.log(row)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
setTimeout(() => {
|
||||
loading.value = false
|
||||
@@ -337,6 +341,7 @@ onMounted(() => {
|
||||
}"
|
||||
sticky
|
||||
class="border border-(--ui-border-accented) rounded-(--ui-radius)"
|
||||
@select="onSelect"
|
||||
>
|
||||
<template #expanded="{ row }">
|
||||
<pre>{{ row.original }}</pre>
|
||||
|
||||
@@ -8,10 +8,10 @@
|
||||
"generate": "nuxi generate"
|
||||
},
|
||||
"dependencies": {
|
||||
"@iconify-json/lucide": "^1.2.28",
|
||||
"@iconify-json/simple-icons": "^1.2.27",
|
||||
"@iconify-json/lucide": "^1.2.30",
|
||||
"@iconify-json/simple-icons": "^1.2.28",
|
||||
"@nuxt/ui": "latest",
|
||||
"@nuxthub/core": "^0.8.17",
|
||||
"nuxt": "^3.15.4"
|
||||
"@nuxthub/core": "^0.8.18",
|
||||
"nuxt": "^3.16.0"
|
||||
}
|
||||
}
|
||||
|
||||
4121
pnpm-lock.yaml
generated
4121
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -6,13 +6,11 @@
|
||||
"enabled": true
|
||||
},
|
||||
"ignoreDeps": [
|
||||
"happy-dom",
|
||||
"valibot30",
|
||||
"valibot31",
|
||||
"typescript",
|
||||
"vaul-vue",
|
||||
"vue-tsc"
|
||||
],
|
||||
"baseBranches": ["dev", "v3"],
|
||||
"baseBranches": ["v2", "v3"],
|
||||
"packageRules": [{
|
||||
"matchBaseBranches": ["v3"],
|
||||
"labels": ["v3"]
|
||||
|
||||
@@ -9,40 +9,40 @@ export interface ModuleOptions {
|
||||
/**
|
||||
* Prefix for components
|
||||
* @defaultValue `U`
|
||||
* @link https://ui3.nuxt.dev/getting-started/installation/nuxt#prefix
|
||||
* @link https://ui.nuxt.com/getting-started/installation/nuxt#prefix
|
||||
*/
|
||||
prefix?: string
|
||||
|
||||
/**
|
||||
* Enable or disable `@nuxt/fonts` module
|
||||
* @defaultValue `true`
|
||||
* @link https://ui3.nuxt.dev/getting-started/installation/nuxt#fonts
|
||||
* @link https://ui.nuxt.com/getting-started/installation/nuxt#fonts
|
||||
*/
|
||||
fonts?: boolean
|
||||
|
||||
/**
|
||||
* Enable or disable `@nuxtjs/color-mode` module
|
||||
* @defaultValue `true`
|
||||
* @link https://ui3.nuxt.dev/getting-started/installation/nuxt#colormode
|
||||
* @link https://ui.nuxt.com/getting-started/installation/nuxt#colormode
|
||||
*/
|
||||
colorMode?: boolean
|
||||
|
||||
/**
|
||||
* Customize how the theme is generated
|
||||
* @link https://ui3.nuxt.dev/getting-started/theme
|
||||
* @link https://ui.nuxt.com/getting-started/theme
|
||||
*/
|
||||
theme?: {
|
||||
/**
|
||||
* Define the color aliases available for components
|
||||
* @defaultValue `['primary', 'secondary', 'success', 'info', 'warning', 'error']`
|
||||
* @link https://ui3.nuxt.dev/getting-started/installation/nuxt#themecolors
|
||||
* @link https://ui.nuxt.com/getting-started/installation/nuxt#themecolors
|
||||
*/
|
||||
colors?: string[]
|
||||
|
||||
/**
|
||||
* Enable or disable transitions on components
|
||||
* @defaultValue `true`
|
||||
* @link https://ui3.nuxt.dev/getting-started/installation/nuxt#themetransitions
|
||||
* @link https://ui.nuxt.com/getting-started/installation/nuxt#themetransitions
|
||||
*/
|
||||
transitions?: boolean
|
||||
}
|
||||
@@ -53,9 +53,9 @@ export default defineNuxtModule<ModuleOptions>({
|
||||
name: 'ui',
|
||||
configKey: 'ui',
|
||||
compatibility: {
|
||||
nuxt: '>=3.13.1'
|
||||
nuxt: '>=3.16.0'
|
||||
},
|
||||
docs: 'https://ui3.nuxt.dev/getting-started/installation/nuxt'
|
||||
docs: 'https://ui.nuxt.com/getting-started/installation/nuxt'
|
||||
},
|
||||
defaults: defaultOptions,
|
||||
async setup(options, nuxt) {
|
||||
|
||||
@@ -20,7 +20,11 @@ export default function ComponentImportPlugin(options: NuxtUIOptions & { prefix:
|
||||
|
||||
const pluginOptions = defu(options.components, <ComponentsOptions>{
|
||||
dts: options.dts ?? true,
|
||||
exclude: [/[\\/]node_modules[\\/](?!\.pnpm|@nuxt\/ui)/, /[\\/]\.git[\\/]/, /[\\/]\.nuxt[\\/]/],
|
||||
exclude: [
|
||||
/[\\/]node_modules[\\/](?!\.pnpm|@nuxt\/ui|@compodium\/examples)/,
|
||||
/[\\/]\.git[\\/]/,
|
||||
/[\\/]\.nuxt[\\/]/
|
||||
],
|
||||
resolvers: [
|
||||
(componentName) => {
|
||||
if (overrideNames.has(componentName))
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
<!-- eslint-disable vue/block-tag-newline -->
|
||||
<script lang="ts">
|
||||
import type { AccordionRootProps, AccordionRootEmits } from 'reka-ui'
|
||||
import type { AppConfig } from '@nuxt/schema'
|
||||
@@ -60,6 +61,7 @@ export type AccordionSlots<T extends { slot?: string }> = {
|
||||
content: SlotProps<T>
|
||||
body: SlotProps<T>
|
||||
} & DynamicSlots<T, SlotProps<T>>
|
||||
|
||||
</script>
|
||||
|
||||
<script setup lang="ts" generic="T extends AccordionItem">
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
<script lang="ts">
|
||||
import type { ConfigProviderProps, TooltipProviderProps } from 'reka-ui'
|
||||
import { localeContextInjectionKey } from '../composables/useLocale'
|
||||
import type { ToasterProps, Locale } from '../types'
|
||||
import type { ToasterProps, Locale, Messages } from '../types'
|
||||
|
||||
export interface AppProps extends Omit<ConfigProviderProps, 'useId' | 'dir' | 'locale'> {
|
||||
export interface AppProps<T extends Messages = Messages> extends Omit<ConfigProviderProps, 'useId' | 'dir' | 'locale'> {
|
||||
tooltip?: TooltipProviderProps
|
||||
toaster?: ToasterProps | null
|
||||
locale?: Locale
|
||||
locale?: Locale<T>
|
||||
}
|
||||
|
||||
export interface AppSlots {
|
||||
@@ -18,14 +17,15 @@ export default {
|
||||
}
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
<script setup lang="ts" generic="T extends Messages = Messages">
|
||||
import { toRef, useId, provide } from 'vue'
|
||||
import { ConfigProvider, TooltipProvider, useForwardProps } from 'reka-ui'
|
||||
import { reactivePick } from '@vueuse/core'
|
||||
import { localeContextInjectionKey } from '../composables/useLocale'
|
||||
import UToaster from './Toaster.vue'
|
||||
import UOverlayProvider from './OverlayProvider.vue'
|
||||
|
||||
const props = defineProps<AppProps>()
|
||||
const props = defineProps<AppProps<T>>()
|
||||
defineSlots<AppSlots>()
|
||||
|
||||
const configProviderProps = useForwardProps(reactivePick(props, 'scrollBody'))
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
<!-- eslint-disable vue/block-tag-newline -->
|
||||
<script lang="ts">
|
||||
import type { AppConfig } from '@nuxt/schema'
|
||||
import _appConfig from '#build/app.config'
|
||||
@@ -51,6 +52,7 @@ export type BreadcrumbSlots<T extends { slot?: string }> = {
|
||||
'item-trailing': SlotProps<T>
|
||||
'separator'(props?: {}): any
|
||||
} & DynamicSlots<T, SlotProps<T>>
|
||||
|
||||
</script>
|
||||
|
||||
<script setup lang="ts" generic="T extends BreadcrumbItem">
|
||||
|
||||
@@ -21,10 +21,12 @@ export interface ButtonProps extends UseComponentIconsProps, Omit<LinkProps, 'ra
|
||||
* @defaultValue 'primary'
|
||||
*/
|
||||
color?: ButtonVariants['color']
|
||||
activeColor?: ButtonVariants['color']
|
||||
/**
|
||||
* @defaultValue 'solid'
|
||||
*/
|
||||
variant?: ButtonVariants['variant']
|
||||
activeVariant?: ButtonVariants['variant']
|
||||
/**
|
||||
* @defaultValue 'md'
|
||||
*/
|
||||
@@ -58,8 +60,13 @@ import { pickLinkProps } from '../utils/link'
|
||||
import UIcon from './Icon.vue'
|
||||
import UAvatar from './Avatar.vue'
|
||||
import ULink from './Link.vue'
|
||||
import ULinkBase from './LinkBase.vue'
|
||||
|
||||
const props = defineProps<ButtonProps>()
|
||||
const props = withDefaults(defineProps<ButtonProps>(), {
|
||||
active: undefined,
|
||||
activeClass: '',
|
||||
inactiveClass: ''
|
||||
})
|
||||
const slots = defineSlots<ButtonSlots>()
|
||||
|
||||
const linkProps = useForwardProps(pickLinkProps(props))
|
||||
@@ -87,7 +94,19 @@ const { isLeading, isTrailing, leadingIconName, trailingIconName } = useComponen
|
||||
computed(() => ({ ...props, loading: isLoading.value }))
|
||||
)
|
||||
|
||||
const ui = computed(() => button({
|
||||
const ui = computed(() => tv({
|
||||
extend: button,
|
||||
variants: {
|
||||
active: {
|
||||
true: {
|
||||
base: props.activeClass
|
||||
},
|
||||
false: {
|
||||
base: props.inactiveClass
|
||||
}
|
||||
}
|
||||
}
|
||||
})({
|
||||
color: props.color,
|
||||
variant: props.variant,
|
||||
size: buttonSize.value,
|
||||
@@ -102,26 +121,37 @@ const ui = computed(() => button({
|
||||
|
||||
<template>
|
||||
<ULink
|
||||
v-slot="{ active, ...slotProps }"
|
||||
:type="type"
|
||||
:disabled="disabled || isLoading"
|
||||
:class="ui.base({ class: [props.class, props.ui?.base] })"
|
||||
v-bind="omit(linkProps, ['type', 'disabled'])"
|
||||
raw
|
||||
@click="onClickWrapper"
|
||||
custom
|
||||
>
|
||||
<slot name="leading">
|
||||
<UIcon v-if="isLeading && leadingIconName" :name="leadingIconName" :class="ui.leadingIcon({ class: props.ui?.leadingIcon })" />
|
||||
<UAvatar v-else-if="!!avatar" :size="((props.ui?.leadingAvatarSize || ui.leadingAvatarSize()) as AvatarProps['size'])" v-bind="avatar" :class="ui.leadingAvatar({ class: props.ui?.leadingAvatar })" />
|
||||
</slot>
|
||||
<ULinkBase
|
||||
v-bind="slotProps"
|
||||
:class="ui.base({
|
||||
class: [props.class, props.ui?.base],
|
||||
active,
|
||||
...(active && activeVariant ? { variant: activeVariant } : {}),
|
||||
...(active && activeColor ? { color: activeColor } : {})
|
||||
})"
|
||||
@click="onClickWrapper"
|
||||
>
|
||||
<slot name="leading">
|
||||
<UIcon v-if="isLeading && leadingIconName" :name="leadingIconName" :class="ui.leadingIcon({ class: props.ui?.leadingIcon, active })" />
|
||||
<UAvatar v-else-if="!!avatar" :size="((props.ui?.leadingAvatarSize || ui.leadingAvatarSize()) as AvatarProps['size'])" v-bind="avatar" :class="ui.leadingAvatar({ class: props.ui?.leadingAvatar, active })" />
|
||||
</slot>
|
||||
|
||||
<slot>
|
||||
<span v-if="label" :class="ui.label({ class: props.ui?.label })">
|
||||
{{ label }}
|
||||
</span>
|
||||
</slot>
|
||||
<slot>
|
||||
<span v-if="label" :class="ui.label({ class: props.ui?.label, active })">
|
||||
{{ label }}
|
||||
</span>
|
||||
</slot>
|
||||
|
||||
<slot name="trailing">
|
||||
<UIcon v-if="isTrailing && trailingIconName" :name="trailingIconName" :class="ui.trailingIcon({ class: props.ui?.trailingIcon })" />
|
||||
</slot>
|
||||
<slot name="trailing">
|
||||
<UIcon v-if="isTrailing && trailingIconName" :name="trailingIconName" :class="ui.trailingIcon({ class: props.ui?.trailingIcon, active })" />
|
||||
</slot>
|
||||
</ULinkBase>
|
||||
</ULink>
|
||||
</template>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
<!-- eslint-disable vue/block-tag-newline -->
|
||||
<script lang="ts">
|
||||
import type { VariantProps } from 'tailwind-variants'
|
||||
import type { MaybeRefOrGetter } from '@vueuse/shared'
|
||||
@@ -70,6 +71,7 @@ export type ColorPickerProps = {
|
||||
class?: any
|
||||
ui?: Partial<typeof colorPicker.slots>
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
<!-- eslint-disable vue/block-tag-newline -->
|
||||
<script lang="ts">
|
||||
import type { ListboxRootProps, ListboxRootEmits } from 'reka-ui'
|
||||
import type { FuseResult } from 'fuse.js'
|
||||
@@ -130,6 +131,7 @@ export type CommandPaletteSlots<G extends { slot?: string }, T extends { slot?:
|
||||
'item-label': SlotProps<T>
|
||||
'item-trailing': SlotProps<T>
|
||||
} & DynamicSlots<G, SlotProps<T>> & DynamicSlots<T, SlotProps<T>>
|
||||
|
||||
</script>
|
||||
|
||||
<script setup lang="ts" generic="G extends CommandPaletteGroup<T>, T extends CommandPaletteItem">
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
<!-- eslint-disable vue/block-tag-newline -->
|
||||
<script lang="ts">
|
||||
import type { VariantProps } from 'tailwind-variants'
|
||||
import type { ContextMenuRootProps, ContextMenuRootEmits, ContextMenuContentProps } from 'reka-ui'
|
||||
@@ -93,6 +94,7 @@ export type ContextMenuSlots<T extends { slot?: string }> = {
|
||||
'item-label': SlotProps<T>
|
||||
'item-trailing': SlotProps<T>
|
||||
} & DynamicSlots<T, SlotProps<T>>
|
||||
|
||||
</script>
|
||||
|
||||
<script setup lang="ts" generic="T extends ContextMenuItem">
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
<!-- eslint-disable vue/block-tag-newline -->
|
||||
<script lang="ts">
|
||||
import type { VariantProps } from 'tailwind-variants'
|
||||
import type { DropdownMenuRootProps, DropdownMenuRootEmits, DropdownMenuContentProps, DropdownMenuArrowProps } from 'reka-ui'
|
||||
@@ -101,6 +102,7 @@ export type DropdownMenuSlots<T extends { slot?: string }> = {
|
||||
'item-label': SlotProps<T>
|
||||
'item-trailing': SlotProps<T>
|
||||
} & DynamicSlots<T, SlotProps<T>>
|
||||
|
||||
</script>
|
||||
|
||||
<script setup lang="ts" generic="T extends DropdownMenuItem">
|
||||
|
||||
@@ -12,14 +12,34 @@ const form = tv({ extend: tv(theme), ...(appConfigForm.ui?.form || {}) })
|
||||
|
||||
export interface FormProps<T extends object> {
|
||||
id?: string | number
|
||||
/** Schema to validate the form state. Supports Standard Schema objects, Yup, Joi, and Superstructs. */
|
||||
schema?: FormSchema<T>
|
||||
/** An object representing the current state of the form. */
|
||||
state: Partial<T>
|
||||
/**
|
||||
* Custom validation function to validate the form state.
|
||||
* @param state - The current state of the form.
|
||||
* @returns A promise that resolves to an array of FormError objects, or an array of FormError objects directly.
|
||||
*/
|
||||
validate?: (state: Partial<T>) => Promise<FormError[]> | FormError[]
|
||||
/**
|
||||
* The list of input events that trigger the form validation.
|
||||
* @defaultValue `['blur', 'change', 'input']`
|
||||
*/
|
||||
validateOn?: FormInputEvents[]
|
||||
/** Disable all inputs inside the form. */
|
||||
disabled?: boolean
|
||||
/**
|
||||
* Delay in milliseconds before validating the form on input events.
|
||||
* @defaultValue `300`
|
||||
*/
|
||||
validateOnInputDelay?: number
|
||||
class?: any
|
||||
/**
|
||||
* If true, schema transformations will be applied to the state on submit.
|
||||
* @defaultValue `true`
|
||||
*/
|
||||
transform?: boolean
|
||||
class?: any
|
||||
onSubmit?: ((event: FormSubmitEvent<T>) => void | Promise<void>) | (() => void | Promise<void>)
|
||||
}
|
||||
|
||||
@@ -29,7 +49,7 @@ export interface FormEmits<T extends object> {
|
||||
}
|
||||
|
||||
export interface FormSlots {
|
||||
default(props?: {}): any
|
||||
default(props?: { errors: FormError[] }): any
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -69,7 +89,7 @@ onMounted(async () => {
|
||||
nestedForms.value.set(event.formId, { validate: event.validate })
|
||||
} else if (event.type === 'detach') {
|
||||
nestedForms.value.delete(event.formId)
|
||||
} else if (props.validateOn?.includes(event.type)) {
|
||||
} else if (props.validateOn?.includes(event.type) && !loading.value) {
|
||||
if (event.type !== 'input') {
|
||||
await _validate({ name: event.name, silent: true, nested: false })
|
||||
} else if (event.eager || blurredFields.has(event.name)) {
|
||||
@@ -121,7 +141,7 @@ const blurredFields = new Set<keyof T>()
|
||||
function resolveErrorIds(errs: FormError[]): FormErrorWithId[] {
|
||||
return errs.map(err => ({
|
||||
...err,
|
||||
id: inputs.value[err.name]?.id
|
||||
id: err?.name ? inputs.value[err.name]?.id : undefined
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -159,12 +179,12 @@ async function _validate(opts: { name?: keyof T | (keyof T)[], silent?: boolean,
|
||||
if (names) {
|
||||
const otherErrors = errors.value.filter(error => !names.some((name) => {
|
||||
const pattern = inputs.value?.[name]?.pattern
|
||||
return name === error.name || (pattern && error.name.match(pattern))
|
||||
return name === error.name || (pattern && error.name?.match(pattern))
|
||||
}))
|
||||
|
||||
const pathErrors = (await getErrors()).filter(error => names.some((name) => {
|
||||
const pattern = inputs.value?.[name]?.pattern
|
||||
return name === error.name || (pattern && error.name.match(pattern))
|
||||
return name === error.name || (pattern && error.name?.match(pattern))
|
||||
}))
|
||||
|
||||
errors.value = otherErrors.concat(pathErrors)
|
||||
@@ -269,6 +289,6 @@ defineExpose<Form<T>>({
|
||||
:class="form({ class: props.class })"
|
||||
@submit.prevent="onSubmitWrapper"
|
||||
>
|
||||
<slot />
|
||||
<slot :errors="errors" />
|
||||
</component>
|
||||
</template>
|
||||
|
||||
@@ -31,7 +31,12 @@ export interface FormFieldProps {
|
||||
*/
|
||||
size?: FormFieldVariants['size']
|
||||
required?: boolean
|
||||
/** If true, validation on input will be active immediately instead of waiting for a blur event. */
|
||||
eagerValidation?: boolean
|
||||
/**
|
||||
* Delay in milliseconds before validating the form on input events.
|
||||
* @defaultValue `300`
|
||||
*/
|
||||
validateOnInputDelay?: number
|
||||
class?: any
|
||||
ui?: Partial<typeof formField.slots>
|
||||
@@ -63,7 +68,7 @@ const ui = computed(() => formField({
|
||||
|
||||
const formErrors = inject<Ref<FormError[]> | null>('form-errors', null)
|
||||
|
||||
const error = computed(() => props.error || formErrors?.value?.find(error => error.name === props.name || (props.errorPattern && error.name.match(props.errorPattern)))?.message)
|
||||
const error = computed(() => props.error || formErrors?.value?.find(error => error.name && (error.name === props.name || (props.errorPattern && error.name.match(props.errorPattern))))?.message)
|
||||
|
||||
const id = ref(useId())
|
||||
// Copies id's initial value to bind aria-attributes such as aria-describedby.
|
||||
|
||||
@@ -82,7 +82,7 @@ const props = withDefaults(defineProps<InputProps>(), {
|
||||
const emits = defineEmits<InputEmits>()
|
||||
const slots = defineSlots<InputSlots>()
|
||||
|
||||
const [modelValue, modelModifiers] = defineModel<string | number>()
|
||||
const [modelValue, modelModifiers] = defineModel<string | number | null>()
|
||||
|
||||
const { emitFormBlur, emitFormInput, emitFormChange, size: formGroupSize, color, id, name, highlight, disabled, emitFormFocus, ariaAttrs } = useFormField<InputProps>(props, { deferInputValidation: true })
|
||||
const { orientation, size: buttonGroupSize } = useButtonGroup<InputProps>(props)
|
||||
@@ -111,15 +111,19 @@ function autoFocus() {
|
||||
}
|
||||
|
||||
// Custom function to handle the v-model properties
|
||||
function updateInput(value: string) {
|
||||
function updateInput(value: string | null) {
|
||||
if (modelModifiers.trim) {
|
||||
value = value.trim()
|
||||
value = value?.trim() ?? null
|
||||
}
|
||||
|
||||
if (modelModifiers.number || props.type === 'number') {
|
||||
value = looseToNumber(value)
|
||||
}
|
||||
|
||||
if (modelModifiers.nullify) {
|
||||
value ||= null
|
||||
}
|
||||
|
||||
modelValue.value = value
|
||||
emitFormInput()
|
||||
}
|
||||
|
||||
@@ -157,7 +157,7 @@ export interface InputMenuSlots<T, M extends boolean> {
|
||||
import { computed, ref, toRef, onMounted, toRaw } from 'vue'
|
||||
import { ComboboxRoot, ComboboxArrow, ComboboxAnchor, ComboboxInput, ComboboxTrigger, ComboboxPortal, ComboboxContent, ComboboxViewport, ComboboxEmpty, ComboboxGroup, ComboboxLabel, ComboboxSeparator, ComboboxItem, ComboboxItemIndicator, TagsInputRoot, TagsInputItem, TagsInputItemText, TagsInputItemDelete, TagsInputInput, useForwardPropsEmits, useFilter } from 'reka-ui'
|
||||
import { defu } from 'defu'
|
||||
import { isEqual } from 'ohash'
|
||||
import { isEqual } from 'ohash/utils'
|
||||
import { reactivePick, createReusableTemplate } from '@vueuse/core'
|
||||
import { useAppConfig } from '#imports'
|
||||
import { useButtonGroup } from '../composables/useButtonGroup'
|
||||
|
||||
@@ -91,7 +91,7 @@ export interface LinkSlots {
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { isEqual, diff } from 'ohash'
|
||||
import { isEqual, diff } from 'ohash/utils'
|
||||
import { useForwardProps } from 'reka-ui'
|
||||
import { reactiveOmit } from '@vueuse/core'
|
||||
import { useRoute } from '#imports'
|
||||
@@ -124,11 +124,15 @@ const ui = computed(() => tv({
|
||||
function isPartiallyEqual(item1: any, item2: any) {
|
||||
const diffedKeys = diff(item1, item2).reduce((filtered, q) => {
|
||||
if (q.type === 'added') {
|
||||
filtered.push(q.key)
|
||||
filtered.add(q.key)
|
||||
}
|
||||
return filtered
|
||||
}, [] as string[])
|
||||
return isEqual(item1, item2, { excludeKeys: key => diffedKeys.includes(key) })
|
||||
}, new Set<string>())
|
||||
|
||||
const item1Filtered = Object.fromEntries(Object.entries(item1).filter(([key]) => !diffedKeys.has(key)))
|
||||
const item2Filtered = Object.fromEntries(Object.entries(item2).filter(([key]) => !diffedKeys.has(key)))
|
||||
|
||||
return isEqual(item1Filtered, item2Filtered)
|
||||
}
|
||||
|
||||
function isLinkActive({ route: linkRoute, isActive, isExactActive }: any) {
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<script lang="ts">
|
||||
import type { LinkProps } from '../types'
|
||||
|
||||
export interface LinkBaseProps {
|
||||
as?: string
|
||||
type?: string
|
||||
@@ -6,8 +8,8 @@ export interface LinkBaseProps {
|
||||
onClick?: ((e: MouseEvent) => void | Promise<void>) | Array<((e: MouseEvent) => void | Promise<void>)>
|
||||
href?: string
|
||||
navigate?: (e: MouseEvent) => void
|
||||
rel?: string
|
||||
target?: string
|
||||
target?: LinkProps['target']
|
||||
rel?: LinkProps['rel']
|
||||
isExternal?: boolean
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
<!-- eslint-disable vue/block-tag-newline -->
|
||||
<script lang="ts">
|
||||
import type { VariantProps } from 'tailwind-variants'
|
||||
import type { NavigationMenuRootProps, NavigationMenuRootEmits, NavigationMenuContentProps, CollapsibleRootProps } from 'reka-ui'
|
||||
@@ -125,6 +126,7 @@ export type NavigationMenuSlots<T extends { slot?: string }> = {
|
||||
'item-trailing': SlotProps<T>
|
||||
'item-content': SlotProps<T>
|
||||
} & DynamicSlots<T, SlotProps<T>>
|
||||
|
||||
</script>
|
||||
|
||||
<script setup lang="ts" generic="T extends MaybeArrayOfArrayItem<I>, I extends MaybeArrayOfArray<NavigationMenuItem>">
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<script lang="ts">
|
||||
import type { PaginationRootProps, PaginationRootEmits } from 'reka-ui'
|
||||
import type { AppConfig } from '@nuxt/schema'
|
||||
import type { RouteLocationRaw } from '#vue-router'
|
||||
import _appConfig from '#build/app.config'
|
||||
import theme from '#build/ui/pagination'
|
||||
import { tv } from '../utils/tv'
|
||||
@@ -78,7 +77,7 @@ export interface PaginationProps extends Partial<Pick<PaginationRootProps, 'defa
|
||||
* A function to render page controls as links.
|
||||
* @param page The page number to navigate to.
|
||||
*/
|
||||
to?: (page: number) => RouteLocationRaw
|
||||
to?: (page: number) => ButtonProps['to']
|
||||
class?: any
|
||||
ui?: Partial<typeof pagination.slots>
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
<!-- eslint-disable vue/block-tag-newline -->
|
||||
<script lang="ts">
|
||||
import type { VariantProps } from 'tailwind-variants'
|
||||
import type { PinInputRootEmits, PinInputRootProps } from 'reka-ui'
|
||||
@@ -45,6 +46,7 @@ export type PinInputEmits = PinInputRootEmits & {
|
||||
change: [payload: Event]
|
||||
blur: [payload: Event]
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user