mirror of
https://github.com/ArthurDanjou/ui.git
synced 2026-01-29 03:10:42 +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
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
value: |
|
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
|
- type: textarea
|
||||||
id: env
|
id: env
|
||||||
attributes:
|
attributes:
|
||||||
@@ -37,7 +37,7 @@ body:
|
|||||||
id: version
|
id: version
|
||||||
attributes:
|
attributes:
|
||||||
label: Version
|
label: Version
|
||||||
placeholder: v3.0.0-alpha.x
|
placeholder: v3.0.0
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: textarea
|
- 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
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
value: |
|
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
|
- type: textarea
|
||||||
id: env
|
id: env
|
||||||
attributes:
|
attributes:
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ body:
|
|||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
value: |
|
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
|
- type: textarea
|
||||||
id: description
|
id: description
|
||||||
attributes:
|
attributes:
|
||||||
|
|||||||
2
.github/ISSUE_TEMPLATE/feature-request.yml
vendored
2
.github/ISSUE_TEMPLATE/feature-request.yml
vendored
@@ -5,7 +5,7 @@ body:
|
|||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
value: |
|
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
|
- type: textarea
|
||||||
id: description
|
id: description
|
||||||
attributes:
|
attributes:
|
||||||
|
|||||||
2
.github/ISSUE_TEMPLATE/question-v3.yml
vendored
2
.github/ISSUE_TEMPLATE/question-v3.yml
vendored
@@ -5,7 +5,7 @@ body:
|
|||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
value: |
|
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
|
- type: textarea
|
||||||
id: description
|
id: description
|
||||||
attributes:
|
attributes:
|
||||||
|
|||||||
2
.github/ISSUE_TEMPLATE/question.yml
vendored
2
.github/ISSUE_TEMPLATE/question.yml
vendored
@@ -5,7 +5,7 @@ body:
|
|||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
value: |
|
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
|
- type: textarea
|
||||||
id: description
|
id: description
|
||||||
attributes:
|
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
|
# 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)
|
## [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
|
### Bug Fixes
|
||||||
|
|||||||
20
README.md
20
README.md
@@ -11,31 +11,31 @@
|
|||||||
[![License][license-src]][license-href]
|
[![License][license-src]][license-href]
|
||||||
[![Nuxt][nuxt-src]][nuxt-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]
|
> [!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
|
## Documentation
|
||||||
|
|
||||||
Visit https://ui3.nuxt.dev to explore the documentation.
|
Visit https://ui.nuxt.com to explore the documentation.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
```bash [pnpm]
|
```bash [pnpm]
|
||||||
pnpm add @nuxt/ui@next
|
pnpm add @nuxt/ui
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash [yarn]
|
```bash [yarn]
|
||||||
yarn add @nuxt/ui@next
|
yarn add @nuxt/ui
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash [npm]
|
```bash [npm]
|
||||||
npm install @nuxt/ui@next
|
npm install @nuxt/ui
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash [bun]
|
```bash [bun]
|
||||||
bun add @nuxt/ui@next
|
bun add @nuxt/ui
|
||||||
```
|
```
|
||||||
|
|
||||||
### Nuxt
|
### Nuxt
|
||||||
@@ -55,7 +55,7 @@ export default defineNuxtConfig({
|
|||||||
@import "@nuxt/ui";
|
@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
|
### Vue
|
||||||
|
|
||||||
@@ -102,7 +102,7 @@ app.mount('#app')
|
|||||||
@import "@nuxt/ui";
|
@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
|
## 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).
|
Licensed under the [MIT license](https://github.com/nuxt/ui/blob/v3/LICENSE.md).
|
||||||
|
|
||||||
<!-- Badges -->
|
<!-- 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-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
|
[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">
|
<script setup lang="ts">
|
||||||
// import { withoutTrailingSlash } from 'ufo'
|
import { withoutTrailingSlash } from 'ufo'
|
||||||
import colors from 'tailwindcss/colors'
|
import colors from 'tailwindcss/colors'
|
||||||
// import { debounce } from 'perfect-debounce'
|
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const appConfig = useAppConfig()
|
const appConfig = useAppConfig()
|
||||||
@@ -12,16 +11,6 @@ const { data: files } = useLazyAsyncData('search', () => queryCollectionSearchSe
|
|||||||
server: false
|
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 links = useLinks()
|
||||||
const color = computed(() => colorMode.value === 'dark' ? (colors as any)[appConfig.ui.colors.neutral][900] : 'white')
|
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; }`)
|
const radius = computed(() => `:root { --ui-radius: ${appConfig.theme.radius}rem; }`)
|
||||||
@@ -33,8 +22,8 @@ useHead({
|
|||||||
{ key: 'theme-color', name: 'theme-color', content: color }
|
{ key: 'theme-color', name: 'theme-color', content: color }
|
||||||
],
|
],
|
||||||
link: [
|
link: [
|
||||||
{ rel: 'icon', type: 'image/svg+xml', href: '/icon.svg' }
|
{ rel: 'icon', type: 'image/svg+xml', href: '/icon.svg' },
|
||||||
// { rel: 'canonical', href: `https://ui.nuxt.com${withoutTrailingSlash(route.path)}` }
|
{ rel: 'canonical', href: `https://ui.nuxt.com${withoutTrailingSlash(route.path)}` }
|
||||||
],
|
],
|
||||||
style: [
|
style: [
|
||||||
{ innerHTML: radius, id: 'nuxt-ui-radius', tagPriority: -2 },
|
{ innerHTML: radius, id: 'nuxt-ui-radius', tagPriority: -2 },
|
||||||
@@ -61,7 +50,7 @@ provide('navigation', mappedNavigation)
|
|||||||
<NuxtLoadingIndicator color="var(--ui-primary)" :height="2" />
|
<NuxtLoadingIndicator color="var(--ui-primary)" :height="2" />
|
||||||
|
|
||||||
<template v-if="!route.path.startsWith('/examples')">
|
<template v-if="!route.path.startsWith('/examples')">
|
||||||
<!-- <Banner /> -->
|
<Banner />
|
||||||
|
|
||||||
<Header :links="links" />
|
<Header :links="links" />
|
||||||
</template>
|
</template>
|
||||||
@@ -75,7 +64,6 @@ provide('navigation', mappedNavigation)
|
|||||||
|
|
||||||
<ClientOnly>
|
<ClientOnly>
|
||||||
<LazyUContentSearch
|
<LazyUContentSearch
|
||||||
v-model:search-term="searchTerm"
|
|
||||||
:files="files"
|
:files="files"
|
||||||
:groups="[{
|
:groups="[{
|
||||||
id: 'framework',
|
id: 'framework',
|
||||||
@@ -95,5 +83,5 @@ provide('navigation', mappedNavigation)
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style>
|
<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>
|
</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>
|
<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>
|
<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>
|
</template>
|
||||||
</UBanner>
|
</UBanner>
|
||||||
</template>
|
</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">
|
<script setup lang="ts">
|
||||||
|
const route = useRoute()
|
||||||
|
|
||||||
const links = [{
|
const links = [{
|
||||||
label: 'Figma',
|
label: 'Figma',
|
||||||
to: '/figma'
|
to: '/figma'
|
||||||
@@ -16,7 +18,7 @@ const links = [{
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<USeparator icon="i-simple-icons-nuxtdotjs" class="h-px" />
|
<USeparator :icon="route.path === '/' ? undefined : 'i-simple-icons-nuxtdotjs'" class="h-px" />
|
||||||
|
|
||||||
<UFooter>
|
<UFooter>
|
||||||
<template #left>
|
<template #left>
|
||||||
|
|||||||
@@ -30,14 +30,18 @@ const mobileLinks = computed(() => props.links.map(link => ({ ...link, defaultOp
|
|||||||
<UHeader :ui="{ left: 'min-w-0' }" :menu="{ shouldScaleBackground: true }">
|
<UHeader :ui="{ left: 'min-w-0' }" :menu="{ shouldScaleBackground: true }">
|
||||||
<template #left>
|
<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">
|
<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 v-if="route.path === '/'" class="w-auto h-6 shrink-0" />
|
||||||
<Logo class="w-auto h-6 shrink-0 ui-only" />
|
<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>
|
</NuxtLink>
|
||||||
|
|
||||||
<UDropdownMenu
|
<UDropdownMenu
|
||||||
v-slot="{ open }"
|
v-slot="{ open }"
|
||||||
:modal="false"
|
: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' }"
|
:ui="{ content: 'w-(--reka-dropdown-menu-trigger-width) min-w-0' }"
|
||||||
size="xs"
|
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">
|
<script setup lang="ts">
|
||||||
import { upperFirst, camelCase } from 'scule'
|
import { upperFirst, camelCase, kebabCase } from 'scule'
|
||||||
import type { ComponentMeta } from 'vue-component-meta'
|
import type { ComponentMeta } from 'vue-component-meta'
|
||||||
import * as theme from '#build/ui'
|
import * as theme from '#build/ui'
|
||||||
import * as themePro from '#build/ui-pro'
|
import * as themePro from '#build/ui-pro'
|
||||||
@@ -112,7 +112,7 @@ const metaProps: ComputedRef<ComponentMeta['props']> = computed(() => {
|
|||||||
<ProseTd>
|
<ProseTd>
|
||||||
<HighlightInlineType v-if="prop.type" :type="prop.type" />
|
<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" />
|
<ComponentPropsLinks v-if="prop.tags?.length" :prop="prop" />
|
||||||
<ComponentPropsSchema v-if="prop.schema" :prop="prop" :ignore="ignore" />
|
<ComponentPropsSchema v-if="prop.schema" :prop="prop" :ignore="ignore" />
|
||||||
|
|||||||
@@ -1,17 +1,20 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { kebabCase } from 'scule'
|
||||||
import type { PropertyMeta } from 'vue-component-meta'
|
import type { PropertyMeta } from 'vue-component-meta'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
prop: PropertyMeta
|
prop: PropertyMeta
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
|
||||||
const links = computed(() => props.prop.tags?.filter((tag: any) => tag.name === 'link'))
|
const links = computed(() => props.prop.tags?.filter((tag: any) => tag.name === 'link'))
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<ProseUl v-if="links?.length">
|
<ProseUl v-if="links?.length">
|
||||||
<ProseLi v-for="link in links" :key="link.name">
|
<ProseLi v-for="(link, index) in links" :key="index">
|
||||||
<MDC :value="link.text ?? ''" class="my-1" />
|
<MDC :value="link.text ?? ''" class="my-1" :cache-key="`${kebabCase(route.path)}-${prop.name}-link-${index}`" />
|
||||||
</ProseLi>
|
</ProseLi>
|
||||||
</ProseUl>
|
</ProseUl>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { kebabCase } from 'scule'
|
||||||
import type { PropertyMeta } from 'vue-component-meta'
|
import type { PropertyMeta } from 'vue-component-meta'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
@@ -6,6 +7,8 @@ const props = defineProps<{
|
|||||||
ignore?: string[]
|
ignore?: string[]
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
|
||||||
function getSchemaProps(schema: PropertyMeta['schema']): any {
|
function getSchemaProps(schema: PropertyMeta['schema']): any {
|
||||||
if (!schema || typeof schema === 'string' || !schema.schema) {
|
if (!schema || typeof schema === 'string' || !schema.schema) {
|
||||||
return []
|
return []
|
||||||
@@ -40,7 +43,7 @@ const schemaProps = computed(() => {
|
|||||||
<ProseLi v-for="schemaProp in schemaProps" :key="schemaProp.name">
|
<ProseLi v-for="schemaProp in schemaProps" :key="schemaProp.name">
|
||||||
<HighlightInlineType :type="`${schemaProp.name}${schemaProp.required === false ? '?' : ''}: ${schemaProp.type}`" />
|
<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>
|
</ProseLi>
|
||||||
</ProseUl>
|
</ProseUl>
|
||||||
</ProseCollapsible>
|
</ProseCollapsible>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { upperFirst, camelCase } from 'scule'
|
import { upperFirst, camelCase, kebabCase } from 'scule'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
prose?: boolean
|
prose?: boolean
|
||||||
@@ -36,7 +36,7 @@ const meta = await fetchComponentMeta(name as any)
|
|||||||
<ProseTd>
|
<ProseTd>
|
||||||
<HighlightInlineType v-if="slot.type" :type="slot.type" />
|
<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>
|
</ProseTd>
|
||||||
</ProseTr>
|
</ProseTr>
|
||||||
</ProseTbody>
|
</ProseTbody>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { murmurHash } from 'ohash'
|
import { hash } from 'ohash'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
type: string
|
type: string
|
||||||
@@ -23,7 +23,7 @@ const type = computed(() => {
|
|||||||
return type
|
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>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -9,21 +9,22 @@ const props = withDefaults(defineProps<{
|
|||||||
|
|
||||||
function getEmojiFlag(locale: string): string {
|
function getEmojiFlag(locale: string): string {
|
||||||
const languageToCountry: Record<string, string> = {
|
const languageToCountry: Record<string, string> = {
|
||||||
ar: 'sa',
|
ar: 'sa', // Arabic -> Saudi Arabia
|
||||||
bn: 'bd',
|
bn: 'bd', // Bengali -> Bangladesh
|
||||||
cs: 'cz',
|
cs: 'cz', // Czech -> Czech Republic (note: modern country code is actually 'cz')
|
||||||
da: 'dk',
|
da: 'dk', // Danish -> Denmark
|
||||||
el: 'gr',
|
el: 'gr', // Greek -> Greece
|
||||||
et: 'ee',
|
et: 'ee', // Estonian -> Estonia
|
||||||
en: 'gb',
|
en: 'gb', // English -> Great Britain
|
||||||
hi: 'in',
|
he: 'il', // Hebrew -> Israel
|
||||||
ja: 'jp',
|
hi: 'in', // Hindi -> India
|
||||||
km: 'kh',
|
ja: 'jp', // Japanese -> Japan
|
||||||
ko: 'kr',
|
km: 'kh', // Khmer -> Cambodia
|
||||||
nb: 'no',
|
ko: 'kr', // Korean -> South Korea
|
||||||
sv: 'se',
|
nb: 'no', // Norwegian Bokmål -> Norway
|
||||||
uk: 'ua',
|
sv: 'se', // Swedish -> Sweden
|
||||||
vi: 'vn'
|
uk: 'ua', // Ukrainian -> Ukraine
|
||||||
|
vi: 'vn' // Vietnamese -> Vietnam
|
||||||
}
|
}
|
||||||
|
|
||||||
const baseLanguage = locale.split('-')[0]?.toLowerCase() || locale
|
const baseLanguage = locale.split('-')[0]?.toLowerCase() || locale
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
const searchTerm = ref('')
|
const searchTerm = ref('')
|
||||||
|
|
||||||
const { data: users, status } = await useFetch('https://jsonplaceholder.typicode.com/users', {
|
const { data: users, status } = await useFetch('https://jsonplaceholder.typicode.com/users', {
|
||||||
|
key: 'command-palette-users',
|
||||||
transform: (data: { id: number, name: string, email: string }[]) => {
|
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}` } })) || []
|
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">
|
<script setup lang="ts">
|
||||||
const { data: users } = await useFetch('https://jsonplaceholder.typicode.com/users', {
|
const { data: users } = await useFetch('https://jsonplaceholder.typicode.com/users', {
|
||||||
|
key: 'command-palette-users',
|
||||||
transform: (data: { id: number, name: string, email: string }[]) => {
|
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}` } })) || []
|
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 searchTermDebounced = refDebounced(searchTerm, 200)
|
||||||
|
|
||||||
const { data: users, status } = await useFetch('https://jsonplaceholder.typicode.com/users', {
|
const { data: users, status } = await useFetch('https://jsonplaceholder.typicode.com/users', {
|
||||||
|
key: 'command-palette-users',
|
||||||
params: { q: searchTermDebounced },
|
params: { q: searchTermDebounced },
|
||||||
transform: (data: { id: number, name: string, email: string }[]) => {
|
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}` } })) || []
|
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 searchTerm = ref('')
|
||||||
|
|
||||||
const { data: users, status } = await useFetch('https://jsonplaceholder.typicode.com/users', {
|
const { data: users, status } = await useFetch('https://jsonplaceholder.typicode.com/users', {
|
||||||
|
key: 'command-palette-users',
|
||||||
params: { q: searchTerm },
|
params: { q: searchTerm },
|
||||||
transform: (data: { id: number, name: string, email: string }[]) => {
|
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}` } })) || []
|
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>
|
</script>
|
||||||
|
|
||||||
<template>
|
<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">
|
<UFormField label="Email" name="email">
|
||||||
<UInput v-model="state.email" />
|
<UInput v-model="state.email" />
|
||||||
</UFormField>
|
</UFormField>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const { data: users, status } = await useFetch('https://jsonplaceholder.typicode.com/users', {
|
const { data: users, status } = await useFetch('https://jsonplaceholder.typicode.com/users', {
|
||||||
|
key: 'typicode-users',
|
||||||
transform: (data: { id: number, name: string }[]) => {
|
transform: (data: { id: number, name: string }[]) => {
|
||||||
return data?.map(user => ({
|
return data?.map(user => ({
|
||||||
label: user.name,
|
label: user.name,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const { data: users, status } = await useFetch('https://jsonplaceholder.typicode.com/users', {
|
const { data: users, status } = await useFetch('https://jsonplaceholder.typicode.com/users', {
|
||||||
|
key: 'typicode-users-email',
|
||||||
transform: (data: { id: number, name: string, email: string }[]) => {
|
transform: (data: { id: number, name: string, email: string }[]) => {
|
||||||
return data?.map(user => ({
|
return data?.map(user => ({
|
||||||
label: user.name,
|
label: user.name,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ const searchTerm = ref('')
|
|||||||
const searchTermDebounced = refDebounced(searchTerm, 200)
|
const searchTermDebounced = refDebounced(searchTerm, 200)
|
||||||
|
|
||||||
const { data: users, status } = await useFetch('https://jsonplaceholder.typicode.com/users', {
|
const { data: users, status } = await useFetch('https://jsonplaceholder.typicode.com/users', {
|
||||||
|
key: 'typicode-users',
|
||||||
params: { q: searchTermDebounced },
|
params: { q: searchTermDebounced },
|
||||||
transform: (data: { id: number, name: string }[]) => {
|
transform: (data: { id: number, name: string }[]) => {
|
||||||
return data?.map(user => ({
|
return data?.map(user => ({
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
const searchTerm = ref('')
|
const searchTerm = ref('')
|
||||||
|
|
||||||
const { data: users, status } = await useFetch('https://jsonplaceholder.typicode.com/users', {
|
const { data: users, status } = await useFetch('https://jsonplaceholder.typicode.com/users', {
|
||||||
|
key: 'command-palette-users',
|
||||||
params: { q: searchTerm },
|
params: { q: searchTerm },
|
||||||
transform: (data: { id: number, name: string, email: string }[]) => {
|
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}` } })) || []
|
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">
|
<script setup lang="ts">
|
||||||
const { data: users, status } = await useFetch('https://jsonplaceholder.typicode.com/users', {
|
const { data: users, status } = await useFetch('https://jsonplaceholder.typicode.com/users', {
|
||||||
|
key: 'typicode-users',
|
||||||
transform: (data: { id: number, name: string }[]) => {
|
transform: (data: { id: number, name: string }[]) => {
|
||||||
return data?.map(user => ({
|
return data?.map(user => ({
|
||||||
label: user.name,
|
label: user.name,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const { data: users, status } = await useFetch('https://jsonplaceholder.typicode.com/users', {
|
const { data: users, status } = await useFetch('https://jsonplaceholder.typicode.com/users', {
|
||||||
|
key: 'typicode-users-email',
|
||||||
transform: (data: { id: number, name: string, email: string }[]) => {
|
transform: (data: { id: number, name: string, email: string }[]) => {
|
||||||
return data?.map(user => ({
|
return data?.map(user => ({
|
||||||
label: user.name,
|
label: user.name,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ const searchTerm = ref('')
|
|||||||
const searchTermDebounced = refDebounced(searchTerm, 200)
|
const searchTermDebounced = refDebounced(searchTerm, 200)
|
||||||
|
|
||||||
const { data: users, status } = await useFetch('https://jsonplaceholder.typicode.com/users', {
|
const { data: users, status } = await useFetch('https://jsonplaceholder.typicode.com/users', {
|
||||||
|
key: 'typicode-users',
|
||||||
params: { q: searchTermDebounced },
|
params: { q: searchTermDebounced },
|
||||||
transform: (data: { id: number, name: string }[]) => {
|
transform: (data: { id: number, name: string }[]) => {
|
||||||
return data?.map(user => ({
|
return data?.map(user => ({
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const { data: users, status } = await useFetch('https://jsonplaceholder.typicode.com/users', {
|
const { data: users, status } = await useFetch('https://jsonplaceholder.typicode.com/users', {
|
||||||
|
key: 'typicode-users',
|
||||||
transform: (data: { id: number, name: string }[]) => {
|
transform: (data: { id: number, name: string }[]) => {
|
||||||
return data?.map(user => ({
|
return data?.map(user => ({
|
||||||
label: user.name,
|
label: user.name,
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ type User = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { data, status } = await useFetch<User[]>('https://jsonplaceholder.typicode.com/users', {
|
const { data, status } = await useFetch<User[]>('https://jsonplaceholder.typicode.com/users', {
|
||||||
|
key: 'table-users',
|
||||||
transform: (data) => {
|
transform: (data) => {
|
||||||
return data?.map(user => ({
|
return data?.map(user => ({
|
||||||
...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
|
title: string
|
||||||
description: string
|
description: string
|
||||||
component: string
|
component: string
|
||||||
module: string
|
module?: string
|
||||||
}>(), {
|
}>(), {
|
||||||
module: ''
|
module: ''
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ withDefaults(defineProps<{
|
|||||||
title: string
|
title: string
|
||||||
description: string
|
description: string
|
||||||
headline: string
|
headline: string
|
||||||
framework: string
|
framework?: string
|
||||||
module: string
|
module?: string
|
||||||
}>(), {
|
}>(), {
|
||||||
framework: 'nuxt',
|
framework: 'nuxt',
|
||||||
module: ''
|
module: ''
|
||||||
|
|||||||
@@ -37,6 +37,10 @@ export const useContentNavigation = (navigation: Ref<ContentNavigationItem[] | u
|
|||||||
return {
|
return {
|
||||||
...item,
|
...item,
|
||||||
children: item.children?.filter((child: any) => {
|
children: item.children?.filter((child: any) => {
|
||||||
|
if (child.path.startsWith('/components')) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
if (child.framework && child.framework !== framework.value) {
|
if (child.framework && child.framework !== framework.value) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,23 +12,17 @@ export function useLinks() {
|
|||||||
to: '/components',
|
to: '/components',
|
||||||
active: route.path === '/components',
|
active: route.path === '/components',
|
||||||
children: [{
|
children: [{
|
||||||
label: 'Layout',
|
label: 'Element',
|
||||||
to: '/components#layout',
|
to: '/components#element',
|
||||||
description: 'Container, grid, divider and responsive layout.',
|
description: 'Button, badge, icon, alert, and small UI elements.',
|
||||||
icon: 'i-lucide-layout',
|
icon: 'i-lucide-mouse-pointer',
|
||||||
active: route.fullPath === '/components#layout'
|
active: route.fullPath === '/components#element'
|
||||||
}, {
|
}, {
|
||||||
label: 'Form',
|
label: 'Form',
|
||||||
to: '/components#form',
|
to: '/components#form',
|
||||||
description: 'Input, select, checkbox, radio and form validation.',
|
description: 'Input, select, checkbox, radio and form validation.',
|
||||||
icon: 'i-lucide-text-cursor-input',
|
icon: 'i-lucide-text-cursor-input',
|
||||||
active: route.fullPath === '/components#form'
|
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',
|
label: 'Data',
|
||||||
to: '/components#data',
|
to: '/components#data',
|
||||||
@@ -47,6 +41,12 @@ export function useLinks() {
|
|||||||
description: 'Modal, tooltip, dialog and popover.',
|
description: 'Modal, tooltip, dialog and popover.',
|
||||||
icon: 'i-lucide-layers',
|
icon: 'i-lucide-layers',
|
||||||
active: route.fullPath === '/components#overlay'
|
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',
|
label: 'Pro',
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import colors from 'tailwindcss/colors'
|
import colors from 'tailwindcss/colors'
|
||||||
// import { debounce } from 'perfect-debounce'
|
|
||||||
import type { NuxtError } from '#app'
|
import type { NuxtError } from '#app'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
@@ -15,16 +14,6 @@ const { data: files } = useLazyAsyncData('search', () => queryCollectionSearchSe
|
|||||||
server: false
|
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 links = useLinks()
|
||||||
const color = computed(() => colorMode.value === 'dark' ? (colors as any)[appConfig.ui.colors.neutral][900] : 'white')
|
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; }`)
|
const radius = computed(() => `:root { --ui-radius: ${appConfig.theme.radius}rem; }`)
|
||||||
@@ -48,7 +37,7 @@ useHead({
|
|||||||
})
|
})
|
||||||
|
|
||||||
useSeoMeta({
|
useSeoMeta({
|
||||||
titleTemplate: '%s - Nuxt UI v3',
|
titleTemplate: '%s - Nuxt UI',
|
||||||
title: String(props.error.statusCode)
|
title: String(props.error.statusCode)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -67,17 +56,16 @@ provide('navigation', mappedNavigation)
|
|||||||
<UApp>
|
<UApp>
|
||||||
<NuxtLoadingIndicator color="#FFF" />
|
<NuxtLoadingIndicator color="#FFF" />
|
||||||
|
|
||||||
<!-- <Banner /> -->
|
<Banner />
|
||||||
|
|
||||||
<Header :links="links" />
|
<Header :links="links" />
|
||||||
|
|
||||||
<UError :error="error" />
|
<UError :error="error" />
|
||||||
|
|
||||||
<!-- <Footer /> -->
|
<Footer />
|
||||||
|
|
||||||
<ClientOnly>
|
<ClientOnly>
|
||||||
<LazyUContentSearch
|
<LazyUContentSearch
|
||||||
v-model:search-term="searchTerm"
|
|
||||||
:files="files"
|
:files="files"
|
||||||
:groups="[{
|
:groups="[{
|
||||||
id: 'framework',
|
id: 'framework',
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<slot />
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</template>
|
</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">
|
<script setup lang="ts">
|
||||||
|
import { kebabCase } from 'scule'
|
||||||
import type { ContentNavigationItem } from '@nuxt/content'
|
import type { ContentNavigationItem } from '@nuxt/content'
|
||||||
import { findPageBreadcrumb, mapContentNavigation } from '#ui-pro/utils/content'
|
import { findPageBreadcrumb, mapContentNavigation } from '#ui-pro/utils/content'
|
||||||
|
|
||||||
@@ -9,7 +10,7 @@ definePageMeta({
|
|||||||
layout: 'docs'
|
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) {
|
if (!page.value) {
|
||||||
throw createError({ statusCode: 404, statusMessage: 'Page not found', fatal: true })
|
throw createError({ statusCode: 404, statusMessage: 'Page not found', fatal: true })
|
||||||
}
|
}
|
||||||
@@ -24,7 +25,7 @@ watch(page, () => {
|
|||||||
}
|
}
|
||||||
}, { immediate: true })
|
}, { immediate: true })
|
||||||
|
|
||||||
const { data: surround } = await useAsyncData(`${route.path}-surround`, () => {
|
const { data: surround } = await useAsyncData(`${kebabCase(route.path)}-surround`, () => {
|
||||||
return queryCollectionItemSurroundings('content', route.path, {
|
return queryCollectionItemSurroundings('content', route.path, {
|
||||||
fields: ['description']
|
fields: ['description']
|
||||||
}).orWhere(group => group.where('framework', '=', framework.value).where('framework', 'IS NULL'))
|
}).orWhere(group => group.where('framework', '=', framework.value).where('framework', 'IS NULL'))
|
||||||
@@ -81,6 +82,8 @@ if (route.path.startsWith('/components')) {
|
|||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
defineOgImageComponent('Docs', {
|
defineOgImageComponent('Docs', {
|
||||||
|
title: page.value.title,
|
||||||
|
description: page.value.description,
|
||||||
headline: breadcrumb.value?.[breadcrumb.value.length - 1]?.label || 'Nuxt UI',
|
headline: breadcrumb.value?.[breadcrumb.value.length - 1]?.label || 'Nuxt UI',
|
||||||
framework: page.value?.framework,
|
framework: page.value?.framework,
|
||||||
module: page.value.module
|
module: page.value.module
|
||||||
@@ -106,23 +109,6 @@ const communityLinks = computed(() => [{
|
|||||||
icon: 'i-lucide-map',
|
icon: 'i-lucide-map',
|
||||||
to: '/roadmap'
|
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>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -137,7 +123,7 @@ const communityLinks = computed(() => [{
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #description>
|
<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>
|
||||||
|
|
||||||
<template v-if="page.links?.length" #links>
|
<template v-if="page.links?.length" #links>
|
||||||
@@ -171,14 +157,9 @@ const communityLinks = computed(() => [{
|
|||||||
|
|
||||||
<UPageLinks title="Community" :links="communityLinks" />
|
<UPageLinks title="Community" :links="communityLinks" />
|
||||||
|
|
||||||
<!-- <USeparator type="dashed" />
|
|
||||||
|
|
||||||
<UPageLinks title="Resources" :links="resourcesLinks" />
|
|
||||||
|
|
||||||
<USeparator type="dashed" />
|
<USeparator type="dashed" />
|
||||||
|
|
||||||
<AdsPro />
|
<AdsCarbon />
|
||||||
<AdsCarbon /> -->
|
|
||||||
</template>
|
</template>
|
||||||
</UContentToc>
|
</UContentToc>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ useSeoMeta({
|
|||||||
ogImage: joinURL(url, '/og-image.png')
|
ogImage: joinURL(url, '/og-image.png')
|
||||||
})
|
})
|
||||||
|
|
||||||
const { data: components } = await useAsyncData('components', () => {
|
const { data: components } = await useAsyncData('all-components', () => {
|
||||||
return queryCollection('content')
|
return queryCollection('content')
|
||||||
.where('path', 'LIKE', '/components/%')
|
.where('path', 'LIKE', '/components/%')
|
||||||
.where('extension', '=', 'md')
|
.where('extension', '=', 'md')
|
||||||
@@ -31,17 +31,13 @@ const componentsPerCategory = computed(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const categories = [{
|
const categories = [{
|
||||||
id: 'layout',
|
id: 'element',
|
||||||
title: 'Layout',
|
title: 'Element',
|
||||||
description: 'Structural components for organizing content, including containers, grids, dividers, and responsive layout systems.'
|
description: 'Core UI building blocks like buttons, badges, icons, avatars, and other fundamental interface elements.'
|
||||||
}, {
|
}, {
|
||||||
id: 'form',
|
id: 'form',
|
||||||
title: 'Form',
|
title: 'Form',
|
||||||
description: 'Interactive form elements including inputs, selects, checkboxes, radio buttons, and advanced form validation components.'
|
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',
|
id: 'data',
|
||||||
title: 'Data',
|
title: 'Data',
|
||||||
@@ -54,6 +50,10 @@ const categories = [{
|
|||||||
id: 'overlay',
|
id: 'overlay',
|
||||||
title: 'Overlay',
|
title: 'Overlay',
|
||||||
description: 'Floating UI elements like modals, dialogs, tooltips, popovers, and other components that overlay the main content.'
|
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()
|
const { y } = useWindowScroll()
|
||||||
@@ -81,7 +81,10 @@ onMounted(() => {
|
|||||||
orientation="vertical"
|
orientation="vertical"
|
||||||
:ui="{ title: 'text-balance', container: 'relative' }"
|
: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>
|
<template #headline>
|
||||||
<UButton
|
<UButton
|
||||||
to="https://tailwindcss.com"
|
to="https://tailwindcss.com"
|
||||||
@@ -96,6 +99,7 @@ onMounted(() => {
|
|||||||
<template #title>
|
<template #title>
|
||||||
Build beautiful UI with <span class="text-(--ui-primary)">{{ components!.length }}+</span> powerful components
|
Build beautiful UI with <span class="text-(--ui-primary)">{{ components!.length }}+</span> powerful components
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #links>
|
<template #links>
|
||||||
<UButton
|
<UButton
|
||||||
to="/getting-started/installation/vue"
|
to="/getting-started/installation/vue"
|
||||||
@@ -114,10 +118,9 @@ onMounted(() => {
|
|||||||
size="xl"
|
size="xl"
|
||||||
/>
|
/>
|
||||||
</template>
|
</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 />
|
||||||
<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" />
|
||||||
</template>
|
|
||||||
</UPageHero>
|
</UPageHero>
|
||||||
|
|
||||||
<div v-for="category in categories" :key="category.id">
|
<div v-for="category in categories" :key="category.id">
|
||||||
@@ -148,6 +151,7 @@ onMounted(() => {
|
|||||||
:description="component.description"
|
:description="component.description"
|
||||||
:to="component.path"
|
:to="component.path"
|
||||||
:ui="{ wrapper: 'order-last', container: 'lg:flex' }"
|
:ui="{ wrapper: 'order-last', container: 'lg:flex' }"
|
||||||
|
class="group"
|
||||||
>
|
>
|
||||||
<template #title>
|
<template #title>
|
||||||
<div class="flex items-center gap-0.5">
|
<div class="flex items-center gap-0.5">
|
||||||
@@ -156,7 +160,7 @@ onMounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</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
|
<UColorModeImage
|
||||||
:light="`${component.path.replace('/components/', '/components/light/')}.png`"
|
:light="`${component.path.replace('/components/', '/components/light/')}.png`"
|
||||||
:dark="`${component.path.replace('/components/', '/components/dark/')}.png`"
|
:dark="`${component.path.replace('/components/', '/components/dark/')}.png`"
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ hero:
|
|||||||
links:
|
links:
|
||||||
- label: Purchase Pro Kit
|
- label: Purchase Pro Kit
|
||||||
to: 'https://nuxt.lemonsqueezy.com/buy/17213c49-621b-4c2c-9478-3a50a099003d'
|
to: 'https://nuxt.lemonsqueezy.com/buy/17213c49-621b-4c2c-9478-3a50a099003d'
|
||||||
trailing-icon: i-lucide-arrow-right
|
|
||||||
target: _blank
|
target: _blank
|
||||||
- label: Free Figma Kit
|
- label: Free Figma Kit
|
||||||
to: 'https://go.nuxt.com/figma'
|
to: 'https://go.nuxt.com/figma'
|
||||||
@@ -121,7 +120,6 @@ section4:
|
|||||||
links:
|
links:
|
||||||
- label: Get access now
|
- label: Get access now
|
||||||
to: 'https://nuxt.lemonsqueezy.com/buy/17213c49-621b-4c2c-9478-3a50a099003d'
|
to: 'https://nuxt.lemonsqueezy.com/buy/17213c49-621b-4c2c-9478-3a50a099003d'
|
||||||
trailing-icon: i-lucide-arrow-right
|
|
||||||
- label: Preview UI Pro Design Kit
|
- label: Preview UI Pro Design Kit
|
||||||
to: 'https://go.nuxt.com/figma-pro'
|
to: 'https://go.nuxt.com/figma-pro'
|
||||||
icon: i-logos-figma
|
icon: i-logos-figma
|
||||||
@@ -180,6 +178,7 @@ pricing:
|
|||||||
- title: Solo License
|
- title: Solo License
|
||||||
description: Design faster with all Nuxt UI Pro components.
|
description: Design faster with all Nuxt UI Pro components.
|
||||||
price: $149
|
price: $149
|
||||||
|
# discount: $119
|
||||||
billing_period: one-time payment
|
billing_period: one-time payment
|
||||||
billing_cycle: plus local taxes
|
billing_cycle: plus local taxes
|
||||||
class: bg-(--ui-bg-elevated)/50
|
class: bg-(--ui-bg-elevated)/50
|
||||||
@@ -201,6 +200,7 @@ pricing:
|
|||||||
- title: Team License
|
- title: Team License
|
||||||
description: Everything you need to deliver faster as a team.
|
description: Everything you need to deliver faster as a team.
|
||||||
price: $349
|
price: $349
|
||||||
|
# discount: $279
|
||||||
billing_period: one-time payment
|
billing_period: one-time payment
|
||||||
billing_cycle: plus local taxes
|
billing_cycle: plus local taxes
|
||||||
class: bg-(--ui-bg-elevated)/50
|
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.
|
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?
|
- label: Do you have a Figma to Code plugin?
|
||||||
content: >
|
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
|
4. Select any Nuxt UI component and inspect the code it generates
|
||||||
|
|
||||||
{.w-full .rounded .mb-2 .max-w-[636px]}
|
{.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.*
|
|
||||||
|
|||||||
@@ -93,10 +93,10 @@ onMounted(async () => {
|
|||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<template #title>
|
<template #title>
|
||||||
<MDC :value="page.hero.title" unwrap="p" />
|
<MDC :value="page.hero.title" unwrap="p" cache-key="figma-hero-title" />
|
||||||
</template>
|
</template>
|
||||||
<template #description>
|
<template #description>
|
||||||
<MDC :value="page.hero.description" unwrap="p" />
|
<MDC :value="page.hero.description" unwrap="p" cache-key="figma-hero-description" />
|
||||||
</template>
|
</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"> -->
|
<!-- <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">
|
<div class="relative">
|
||||||
@@ -140,10 +140,10 @@ onMounted(async () => {
|
|||||||
class="rounded-none bg-gradient-to-b from-(--ui-bg-muted) to-(--ui-bg)"
|
class="rounded-none bg-gradient-to-b from-(--ui-bg-muted) to-(--ui-bg)"
|
||||||
>
|
>
|
||||||
<template #title>
|
<template #title>
|
||||||
<MDC :value="page.cta1.title" unwrap="p" />
|
<MDC :value="page.cta1.title" unwrap="p" cache-key="figma-cta-1-title" />
|
||||||
</template>
|
</template>
|
||||||
<template #description>
|
<template #description>
|
||||||
<MDC :value="page.cta1.description" unwrap="p" />
|
<MDC :value="page.cta1.description" unwrap="p" cache-key="figma-cta-1-description" />
|
||||||
</template>
|
</template>
|
||||||
</UPageCTA>
|
</UPageCTA>
|
||||||
<UPageSection v-bind="page.section1" orientation="horizontal" :ui="{ container: 'py-16 sm:py-16 lg:py-16' }">
|
<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>
|
<template #description>
|
||||||
<MDC :value="page.section4.description" unwrap="p" />
|
<MDC :value="page.section4.description" unwrap="p" cache-key="figma-section-4-description" />
|
||||||
</template>
|
</template>
|
||||||
<div aria-hidden="true" class="absolute z-[-1] border-x border-(--ui-border) inset-0 mx-4 sm:mx-6 lg:mx-8" />
|
<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)">
|
<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"
|
:title="plan.title"
|
||||||
:description="plan.description"
|
:description="plan.description"
|
||||||
:price="plan.price"
|
:price="plan.price"
|
||||||
|
:discount="plan.discount"
|
||||||
:billing-period="plan.billing_period"
|
:billing-period="plan.billing_period"
|
||||||
:billing-cycle="plan.billing_cycle"
|
:billing-cycle="plan.billing_cycle"
|
||||||
:highlight="plan.highlight"
|
:highlight="plan.highlight"
|
||||||
@@ -245,7 +246,7 @@ onMounted(async () => {
|
|||||||
<template #features>
|
<template #features>
|
||||||
<li v-for="(feature, i) in plan.features" :key="i" class="flex items-center gap-2 min-w-0">
|
<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)" />
|
<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>
|
</li>
|
||||||
</template>
|
</template>
|
||||||
<template #button>
|
<template #button>
|
||||||
@@ -281,8 +282,8 @@ onMounted(async () => {
|
|||||||
:items="(page.faq.items as any[])"
|
:items="(page.faq.items as any[])"
|
||||||
class="max-w-4xl mx-auto"
|
class="max-w-4xl mx-auto"
|
||||||
>
|
>
|
||||||
<template #body="{ item }">
|
<template #body="{ item, index }">
|
||||||
<MDC :value="item.content" unwrap="p" />
|
<MDC :value="item.content" unwrap="p" :cache-key="`figma-faq-${index}-content`" />
|
||||||
</template>
|
</template>
|
||||||
</UPageAccordion>
|
</UPageAccordion>
|
||||||
</UPageSection>
|
</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
|
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.
|
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
|
orientation: horizontal
|
||||||
price: $149 - $349
|
price: $149
|
||||||
|
# discount: $119
|
||||||
|
billing_period: one-time payment
|
||||||
|
billing_cycle: plus local taxes
|
||||||
terms: Solo & Team licenses available.
|
terms: Solo & Team licenses available.
|
||||||
features:
|
features:
|
||||||
- 1700+ components & variants from Nuxt UI & UI Pro
|
- 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)
|
- 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)
|
- Includes Nuxt UI [Figma Kit](https://www.figma.com/community/file/1288455405058138934)
|
||||||
button:
|
button:
|
||||||
label: Explore Figma Kit Pricing
|
label: Buy Figma Kit
|
||||||
to: 'https://nuxt.lemonsqueezy.com/buy/17213c49-621b-4c2c-9478-3a50a099003d'
|
to: 'https://nuxt.lemonsqueezy.com/buy/17213c49-621b-4c2c-9478-3a50a099003d'
|
||||||
color: 'neutral'
|
color: 'neutral'
|
||||||
plans:
|
plans:
|
||||||
- title: Solo
|
- title: Solo
|
||||||
description: Tailored for indie hackers, freelancers and solo founders.
|
description: Tailored for indie hackers, freelancers and solo founders.
|
||||||
price: $249
|
price: $249
|
||||||
|
# discount: $199
|
||||||
billing_period: one-time payment
|
billing_period: one-time payment
|
||||||
billing_cycle: plus local taxes
|
billing_cycle: plus local taxes
|
||||||
features:
|
features:
|
||||||
@@ -50,6 +54,7 @@ pricing:
|
|||||||
- title: Startup
|
- title: Startup
|
||||||
description: Best suited for small teams, startups and agencies.
|
description: Best suited for small teams, startups and agencies.
|
||||||
price: $499
|
price: $499
|
||||||
|
# discount: $399
|
||||||
billing_period: one-time payment
|
billing_period: one-time payment
|
||||||
billing_cycle: plus local taxes
|
billing_cycle: plus local taxes
|
||||||
features:
|
features:
|
||||||
@@ -65,6 +70,7 @@ pricing:
|
|||||||
- title: Organization
|
- title: Organization
|
||||||
description: Ideal for larger teams and organizations.
|
description: Ideal for larger teams and organizations.
|
||||||
price: $999
|
price: $999
|
||||||
|
# discount: $799
|
||||||
billing_period: one-time payment
|
billing_period: one-time payment
|
||||||
billing_cycle: plus local taxes
|
billing_cycle: plus local taxes
|
||||||
features:
|
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."
|
- 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:
|
user:
|
||||||
name: 'Estéban Soubiran'
|
name: 'Estéban Soubiran'
|
||||||
description: 'Web developer and UnJS member'
|
description: 'Software engineer'
|
||||||
to: 'https://x.com/soubiran_'
|
to: 'https://x.com/soubiran_'
|
||||||
target: _blank
|
target: _blank
|
||||||
avatar:
|
avatar:
|
||||||
|
|||||||
@@ -7,13 +7,13 @@ hero:
|
|||||||
- label: Buy a license
|
- label: Buy a license
|
||||||
size: xl
|
size: xl
|
||||||
to: /pro/pricing
|
to: /pro/pricing
|
||||||
trailing-icon: i-lucide-arrow-right
|
- label: Try for free
|
||||||
- label: Get started
|
|
||||||
trailing: true
|
trailing: true
|
||||||
color: neutral
|
color: neutral
|
||||||
variant: ghost
|
variant: outline
|
||||||
to: /getting-started/installation/pro/nuxt
|
to: /getting-started/installation/pro/nuxt
|
||||||
size: xl
|
size: xl
|
||||||
|
trailingIcon: i-lucide-arrow-right
|
||||||
features:
|
features:
|
||||||
title: Create stunning Vue applications faster.
|
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.
|
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:
|
links:
|
||||||
- label: Buy a license
|
- label: Buy a license
|
||||||
to: '/pro/pricing'
|
to: '/pro/pricing'
|
||||||
trailing-icon: i-lucide-arrow-right
|
- label: Try for free
|
||||||
- label: Get started
|
to: /getting-started/installation/pro/nuxt
|
||||||
to: '/getting-started/license'
|
variant: outline
|
||||||
variant: ghost
|
|
||||||
color: neutral
|
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.'
|
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
|
navigation: false
|
||||||
links:
|
links:
|
||||||
- label: Get started
|
- label: Buy a license
|
||||||
to: /getting-started/installation/pro/nuxt#use-an-official-template
|
color: primary
|
||||||
color: neutral
|
|
||||||
size: xl
|
size: xl
|
||||||
trailingIcon: i-lucide-arrow-right
|
to: /pro/pricing
|
||||||
- label: Purchase a license
|
- label: Try for free
|
||||||
|
to: /getting-started/installation/pro/nuxt#use-an-official-template
|
||||||
size: xl
|
size: xl
|
||||||
color: neutral
|
color: neutral
|
||||||
variant: outline
|
variant: outline
|
||||||
to: /pro/pricing
|
trailingIcon: i-lucide-arrow-right
|
||||||
templates:
|
templates:
|
||||||
- title: 'Dashboard'
|
- 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."
|
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
|
- title: Resizable multi-column layout
|
||||||
icon: i-lucide-columns-3
|
icon: i-lucide-columns-3
|
||||||
links:
|
links:
|
||||||
- label: Live Preview
|
- label: Preview
|
||||||
to: https://dashboard-template.nuxt.dev
|
to: https://dashboard-template.nuxt.dev
|
||||||
target: _blank
|
target: _blank
|
||||||
leadingIcon: i-logos-nuxt-icon
|
leadingIcon: i-logos-nuxt-icon
|
||||||
@@ -42,7 +42,7 @@ templates:
|
|||||||
icon: i-simple-icons-github
|
icon: i-simple-icons-github
|
||||||
color: neutral
|
color: neutral
|
||||||
variant: outline
|
variant: outline
|
||||||
- label: Live Preview
|
- label: Preview
|
||||||
to: https://vue-dashboard-template.nuxt.dev
|
to: https://vue-dashboard-template.nuxt.dev
|
||||||
target: _blank
|
target: _blank
|
||||||
leadingIcon: i-logos-vue
|
leadingIcon: i-logos-vue
|
||||||
@@ -68,7 +68,7 @@ templates:
|
|||||||
- title: Authentication pages (login, register)
|
- title: Authentication pages (login, register)
|
||||||
icon: i-lucide-user-round-check
|
icon: i-lucide-user-round-check
|
||||||
links:
|
links:
|
||||||
- label: Live Preview
|
- label: Preview
|
||||||
to: https://saas-template.nuxt.dev
|
to: https://saas-template.nuxt.dev
|
||||||
target: _blank
|
target: _blank
|
||||||
leadingIcon: i-logos-nuxt-icon
|
leadingIcon: i-logos-nuxt-icon
|
||||||
@@ -94,7 +94,7 @@ templates:
|
|||||||
- title: Write content in YAML
|
- title: Write content in YAML
|
||||||
icon: i-simple-icons-yaml
|
icon: i-simple-icons-yaml
|
||||||
links:
|
links:
|
||||||
- label: Live Preview
|
- label: Preview
|
||||||
to: https://landing-template.nuxt.dev
|
to: https://landing-template.nuxt.dev
|
||||||
target: _blank
|
target: _blank
|
||||||
leadingIcon: i-logos-nuxt-icon
|
leadingIcon: i-logos-nuxt-icon
|
||||||
@@ -120,7 +120,7 @@ templates:
|
|||||||
- title: Full-text search out of the box
|
- title: Full-text search out of the box
|
||||||
icon: i-lucide-search
|
icon: i-lucide-search
|
||||||
links:
|
links:
|
||||||
- label: Live Preview
|
- label: Preview
|
||||||
to: https://docs-template.nuxt.dev
|
to: https://docs-template.nuxt.dev
|
||||||
target: _blank
|
target: _blank
|
||||||
leadingIcon: i-logos-nuxt-icon
|
leadingIcon: i-logos-nuxt-icon
|
||||||
@@ -144,7 +144,7 @@ templates:
|
|||||||
- title: Nuxt 4 Compatibility Enabled
|
- title: Nuxt 4 Compatibility Enabled
|
||||||
icon: i-simple-icons-nuxtdotjs
|
icon: i-simple-icons-nuxtdotjs
|
||||||
links:
|
links:
|
||||||
- label: Live Preview
|
- label: Preview
|
||||||
to: https://ui-pro-starter.nuxt.dev
|
to: https://ui-pro-starter.nuxt.dev
|
||||||
target: _blank
|
target: _blank
|
||||||
leadingIcon: i-logos-nuxt-icon
|
leadingIcon: i-logos-nuxt-icon
|
||||||
@@ -156,7 +156,7 @@ templates:
|
|||||||
variant: outline
|
variant: outline
|
||||||
icon: i-simple-icons-github
|
icon: i-simple-icons-github
|
||||||
color: neutral
|
color: neutral
|
||||||
- label: Live Preview
|
- label: Preview
|
||||||
to: https://ui-pro-starter-vue.nuxt.dev
|
to: https://ui-pro-starter-vue.nuxt.dev
|
||||||
target: _blank
|
target: _blank
|
||||||
leadingIcon: i-logos-vue
|
leadingIcon: i-logos-vue
|
||||||
|
|||||||
@@ -70,14 +70,12 @@ onMounted(() => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<UMain>
|
<UMain>
|
||||||
<UPageHero headline="License Activation" :title="title" :description="description" :ui="{ container: 'relative' }">
|
<UPageHero headline="License Activation" :title="title" :description="description" :ui="{ container: 'relative overflow-hidden', wrapper: 'lg:px-12', description: 'text-pretty' }">
|
||||||
<template #top>
|
<StarsBg />
|
||||||
<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" />
|
||||||
|
|
||||||
<div class="lg:border-y border-(--ui-border)">
|
<div class="px-4 py-10 lg:border border-(--ui-border) bg-(--ui-bg)">
|
||||||
<UCard class="lg:w-1/2 m-auto lg:rounded-none overflow-hidden" variant="outline" :ui="{ footer: 'bg-(--ui-bg-muted)' }">
|
<div class="max-w-xl mx-auto">
|
||||||
<UForm
|
<UForm
|
||||||
:schema="schema"
|
:schema="schema"
|
||||||
:validate-on="['blur']"
|
:validate-on="['blur']"
|
||||||
@@ -107,12 +105,13 @@ onMounted(() => {
|
|||||||
</UAlert>
|
</UAlert>
|
||||||
<UAlert v-else-if="errorMessage" color="error" variant="subtle" :title="errorMessage" />
|
<UAlert v-else-if="errorMessage" color="error" variant="subtle" :title="errorMessage" />
|
||||||
</UForm>
|
</UForm>
|
||||||
<template #footer>
|
|
||||||
<p class="text-sm text-center text-neutral-500 dark:text-neutral-400">
|
<ProseHr />
|
||||||
If you purchased a license with multiple seats, activate the license key for each member of your team.
|
|
||||||
</p>
|
<ProseNote>
|
||||||
</template>
|
If you purchased a license with multiple seats, activate the license key for each member of your team.
|
||||||
</UCard>
|
</ProseNote>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</UPageHero>
|
</UPageHero>
|
||||||
</UMain>
|
</UMain>
|
||||||
|
|||||||
@@ -26,15 +26,14 @@ useSeoMeta({
|
|||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<template #title>
|
<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>
|
||||||
<template #description>
|
<template #description>
|
||||||
<MDC :value="page.hero.description" unwrap="p" />
|
<MDC :value="page.hero.description" tag="span" unwrap="p" cache-key="pro-hero-description" />
|
||||||
</template>
|
|
||||||
<template #top>
|
|
||||||
<StarsBg />
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<StarsBg />
|
||||||
|
|
||||||
<Motion as-child :initial="{ height: 0 }" :animate="{ height: 'auto' }" :transition="{ delay: 0.2, duration: 1 }">
|
<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" />
|
<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>
|
</Motion>
|
||||||
@@ -83,11 +82,11 @@ useSeoMeta({
|
|||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<template #description>
|
<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 }">
|
<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" unwrap="p" class="before:content-[open-quote] after:content-[close-quote] " />
|
<MDC :value="page.testimonial.quote" tag="span" unwrap="p" class="before:content-[open-quote] after:content-[close-quote]" cache-key="pro-testimonial-quote" />
|
||||||
</Motion>
|
</Motion>
|
||||||
</template>
|
</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
|
<UUser
|
||||||
v-bind="page.testimonial.user"
|
v-bind="page.testimonial.user"
|
||||||
class="justify-center"
|
class="justify-center"
|
||||||
@@ -104,7 +103,7 @@ useSeoMeta({
|
|||||||
}"
|
}"
|
||||||
class="border-t border-(--ui-border)"
|
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" />
|
<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>
|
</Motion>
|
||||||
</UPageSection>
|
</UPageSection>
|
||||||
@@ -117,13 +116,13 @@ useSeoMeta({
|
|||||||
wrapper: 'grid grid-cols-1 lg:grid-cols-2',
|
wrapper: 'grid grid-cols-1 lg:grid-cols-2',
|
||||||
description: 'lg:mt-0' }"
|
description: 'lg:mt-0' }"
|
||||||
orientation="horizontal"
|
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>
|
<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>
|
||||||
<template #description>
|
<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>
|
</template>
|
||||||
</UPageCTA>
|
</UPageCTA>
|
||||||
<UPageSection
|
<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' : ''
|
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>
|
||||||
|
|
||||||
<UPageSection
|
<UPageSection
|
||||||
@@ -198,6 +197,7 @@ useSeoMeta({
|
|||||||
orientation="horizontal"
|
orientation="horizontal"
|
||||||
>
|
>
|
||||||
<StarsBg />
|
<StarsBg />
|
||||||
|
|
||||||
<video
|
<video
|
||||||
class="rounded-[var(--ui-radius)] z-10"
|
class="rounded-[var(--ui-radius)] z-10"
|
||||||
preload="none"
|
preload="none"
|
||||||
|
|||||||
@@ -24,11 +24,10 @@ useSeoMeta({
|
|||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<template #title>
|
<template #title>
|
||||||
<MDC :value="page.pricing.title" unwrap="p" />
|
<MDC :value="page.pricing.title" unwrap="p" cache-key="pro-pricing-title" />
|
||||||
</template>
|
|
||||||
<template #top>
|
|
||||||
<StarsBg />
|
|
||||||
</template>
|
</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 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">
|
<div class="flex flex-col bg-(--ui-bg) gap-8 lg:gap-0">
|
||||||
<UPricingPlan
|
<UPricingPlan
|
||||||
@@ -43,6 +42,7 @@ useSeoMeta({
|
|||||||
:title="plan.title"
|
:title="plan.title"
|
||||||
:description="plan.description"
|
:description="plan.description"
|
||||||
:price="plan.price"
|
:price="plan.price"
|
||||||
|
:discount="plan.discount"
|
||||||
:billing-period="plan.billing_period"
|
:billing-period="plan.billing_period"
|
||||||
:billing-cycle="plan.billing_cycle"
|
:billing-cycle="plan.billing_cycle"
|
||||||
:variant="plan.highlight ? 'soft' : 'outline'"
|
:variant="plan.highlight ? 'soft' : 'outline'"
|
||||||
@@ -54,12 +54,14 @@ useSeoMeta({
|
|||||||
<UPricingPlan
|
<UPricingPlan
|
||||||
v-bind="page.pricing.figma"
|
v-bind="page.pricing.figma"
|
||||||
variant="naked"
|
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)"
|
class="lg:rounded-none border lg:border-y-0 border-(--ui-border)"
|
||||||
>
|
>
|
||||||
<template #features>
|
<template #features>
|
||||||
<li v-for="(feature, index) in page.pricing.figma.features" :key="index" class="flex items-center gap-2 min-w-0">
|
<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" />
|
<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>
|
</li>
|
||||||
</template>
|
</template>
|
||||||
</UPricingPlan>
|
</UPricingPlan>
|
||||||
@@ -111,8 +113,8 @@ useSeoMeta({
|
|||||||
:items="(page.faq.items as any[])"
|
:items="(page.faq.items as any[])"
|
||||||
class="max-w-4xl mx-auto"
|
class="max-w-4xl mx-auto"
|
||||||
>
|
>
|
||||||
<template #body="{ item }">
|
<template #body="{ item, index }">
|
||||||
<MDC :value="item.content" unwrap="p" />
|
<MDC :value="item.content" unwrap="p" :cache-key="`pro-pricing-faq-${index}-content`" />
|
||||||
</template>
|
</template>
|
||||||
</UPageAccordion>
|
</UPageAccordion>
|
||||||
</UPageSection>
|
</UPageSection>
|
||||||
|
|||||||
@@ -16,59 +16,60 @@ useSeoMeta({
|
|||||||
|
|
||||||
<!-- eslint-disable vue/no-v-html -->
|
<!-- eslint-disable vue/no-v-html -->
|
||||||
<template>
|
<template>
|
||||||
<UPageHero :links="page.links" :ui="{ container: 'relative' }">
|
<div class="relative">
|
||||||
<template #top>
|
<UPageHero :links="page.links" :ui="{ container: 'relative' }">
|
||||||
<StarsBg />
|
<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>
|
<template #title>
|
||||||
<MDC :value="page.hero.title" unwrap="p" />
|
<MDC :value="page.hero.title" unwrap="p" cache-key="pro-templates-hero-title" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #description>
|
<template #description>
|
||||||
<MDC :value="page.hero.description" unwrap="p" />
|
<MDC :value="page.hero.description" unwrap="p" cache-key="pro-templates-hero-description" />
|
||||||
</template>
|
</template>
|
||||||
</UPageHero>
|
</UPageHero>
|
||||||
|
|
||||||
<UPageSection
|
<UPageSection
|
||||||
v-for="(template, index) in page.templates"
|
v-for="(template, index) in page.templates"
|
||||||
:key="index"
|
:key="index"
|
||||||
:title="template.title"
|
:title="template.title"
|
||||||
:links="template.links"
|
:links="template.links"
|
||||||
:features="template.features"
|
:features="template.features"
|
||||||
orientation="horizontal"
|
orientation="horizontal"
|
||||||
class="lg:border-t border-(--ui-border)"
|
class="lg:border-t border-(--ui-border)"
|
||||||
:ui="{
|
:ui="{
|
||||||
title: 'lg:text-4xl',
|
title: 'lg:text-4xl',
|
||||||
wrapper: 'lg:py-16 lg:border-r border-(--ui-border) order-last lg:pr-16',
|
wrapper: 'lg:py-16 lg:border-r border-(--ui-border) order-last lg:pr-16',
|
||||||
container: 'lg:py-0'
|
container: 'lg:py-0',
|
||||||
}"
|
links: 'gap-x-3'
|
||||||
>
|
}"
|
||||||
<template #description>
|
>
|
||||||
<MDC :value="template.description" unwrap="p" />
|
<template #description>
|
||||||
</template>
|
<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">
|
<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 }">
|
<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
|
<UColorModeImage
|
||||||
v-if="template.thumbnail"
|
v-if="template.thumbnail"
|
||||||
v-bind="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"
|
class="w-full h-auto border lg:border-y lg:border-x-0 border-(--ui-border) rounded-(--ui-radius) lg:rounded-none"
|
||||||
width="656"
|
width="656"
|
||||||
height="369"
|
height="369"
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
/>
|
/>
|
||||||
<UCarousel
|
<UCarousel
|
||||||
v-else-if="template.images"
|
v-else-if="template.images"
|
||||||
v-slot="{ item }"
|
v-slot="{ item }"
|
||||||
:items="(template.images as any[])"
|
:items="(template.images as any[])"
|
||||||
dots
|
dots
|
||||||
>
|
>
|
||||||
<NuxtImg v-bind="item" class="w-full h-full object-cover" width="576" height="360" />
|
<NuxtImg v-bind="item" class="w-full h-full object-cover" width="576" height="360" />
|
||||||
</UCarousel>
|
</UCarousel>
|
||||||
<Placeholder v-else class="w-full h-full aspect-video" />
|
<Placeholder v-else class="w-full h-full aspect-video" />
|
||||||
</Motion>
|
</Motion>
|
||||||
</div>
|
</div>
|
||||||
</UPageSection>
|
</UPageSection>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ const title = 'Roadmap'
|
|||||||
const description = 'Discover our Volta board for @nuxt/ui development status.'
|
const description = 'Discover our Volta board for @nuxt/ui development status.'
|
||||||
|
|
||||||
useSeoMeta({
|
useSeoMeta({
|
||||||
titleTemplate: '%s - Nuxt UI v3',
|
titleTemplate: '%s - Nuxt UI',
|
||||||
title,
|
title,
|
||||||
ogTitle: 'Nuxt UI Roadmap',
|
ogTitle: 'Nuxt UI Roadmap',
|
||||||
description
|
description
|
||||||
|
|||||||
@@ -1,14 +1,9 @@
|
|||||||
---
|
---
|
||||||
navigation.title: Introduction
|
title: Introduction
|
||||||
title: Nuxt UI v3
|
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.'
|
||||||
description: 'A comprehensive, Nuxt-integrated UI library providing a rich set of fully-styled, accessible and highly customizable components for building modern web applications.'
|
|
||||||
navigation.icon: i-lucide-house
|
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>
|
<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
|
### Reka UI
|
||||||
@@ -24,7 +19,7 @@ This transition empowers Nuxt UI to become a more comprehensive and flexible UI
|
|||||||
|
|
||||||
### Tailwind CSS v4
|
### 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.
|
- **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.
|
- **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
|
### 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**:
|
- **Enhanced Auto-completion**:
|
||||||
- Full auto-completion for component props based on your theme
|
- Full auto-completion for component props based on your theme
|
||||||
@@ -112,11 +107,7 @@ Key points to consider:
|
|||||||
## FAQ
|
## FAQ
|
||||||
|
|
||||||
::accordion
|
::accordion
|
||||||
::accordion-item{label="What are the main considerations when upgrading to Nuxt UI v3?"}
|
::accordion-item{label="Is Nuxt UI compatible with standalone Vue projects?"}
|
||||||
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?"}
|
|
||||||
Nuxt UI is now compatible with Vue! You can follow the [installation guide](/getting-started/installation/vue) to get started.
|
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.
|
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?"}
|
::accordion-item{label="Will Nuxt UI 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.
|
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?"}
|
::accordion-item{label="How does Nuxt UI 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).
|
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?"}
|
::accordion-item{label="What is the testing approach for Nuxt UI?"}
|
||||||
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.
|
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.
|
||||||
::
|
|
||||||
|
|
||||||
::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).
|
|
||||||
::
|
::
|
||||||
::
|
::
|
||||||
|
|
||||||
:hr
|
: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"}
|
::steps{level="4"}
|
||||||
|
|
||||||
#### Install the Nuxt UI v3 beta package
|
#### Install the Nuxt UI package
|
||||||
|
|
||||||
::code-group{sync="pm"}
|
::code-group{sync="pm"}
|
||||||
|
|
||||||
```bash [pnpm]
|
```bash [pnpm]
|
||||||
pnpm add @nuxt/ui@next
|
pnpm add @nuxt/ui
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash [yarn]
|
```bash [yarn]
|
||||||
yarn add @nuxt/ui@next
|
yarn add @nuxt/ui
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash [npm]
|
```bash [npm]
|
||||||
npm install @nuxt/ui@next
|
npm install @nuxt/ui
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash [bun]
|
```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": {
|
"editor.quickSuggestions": {
|
||||||
"strings": "on"
|
"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
|
### 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:
|
Create a new project locally by running the following command:
|
||||||
|
|
||||||
```bash [Terminal]
|
```bash [Terminal]
|
||||||
npx nuxi init -t ui3 <my-app>
|
npx nuxi init -t ui <my-app>
|
||||||
```
|
```
|
||||||
|
|
||||||
::note
|
::note
|
||||||
@@ -225,30 +229,19 @@ This option adds the `transition-colors` class on components with hover or activ
|
|||||||
|
|
||||||
## Continuous Releases
|
## 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"}
|
```diff [package.json]
|
||||||
|
{
|
||||||
```bash [pnpm]
|
"dependencies": {
|
||||||
pnpm add https://pkg.pr.new/@nuxt/ui@5385f84
|
- "@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
|
::note
|
||||||
**pkg.pr.new** will automatically comment on PRs with the installation URL, making it easy to test changes.
|
**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"}
|
::steps{level="4"}
|
||||||
|
|
||||||
#### Install the Nuxt UI v3 beta package
|
#### Install the Nuxt UI package
|
||||||
|
|
||||||
::code-group{sync="pm"}
|
::code-group{sync="pm"}
|
||||||
|
|
||||||
```bash [pnpm]
|
```bash [pnpm]
|
||||||
pnpm add @nuxt/ui@next
|
pnpm add @nuxt/ui
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash [yarn]
|
```bash [yarn]
|
||||||
yarn add @nuxt/ui@next
|
yarn add @nuxt/ui
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash [npm]
|
```bash [npm]
|
||||||
npm install @nuxt/ui@next
|
npm install @nuxt/ui
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash [bun]
|
```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": {
|
"editor.quickSuggestions": {
|
||||||
"strings": "on"
|
"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
|
### 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:
|
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
|
## 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"}
|
```diff [package.json]
|
||||||
|
{
|
||||||
```bash [pnpm]
|
"dependencies": {
|
||||||
pnpm add https://pkg.pr.new/@nuxt/ui@5385f84
|
- "@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
|
::note
|
||||||
**pkg.pr.new** will automatically comment on PRs with the installation URL, making it easy to test changes.
|
**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"}
|
::::code-group{sync="pm"}
|
||||||
|
|
||||||
```bash [pnpm]
|
```bash [pnpm]
|
||||||
pnpm add @nuxt/ui@next
|
pnpm add @nuxt/ui
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash [yarn]
|
```bash [yarn]
|
||||||
yarn add @nuxt/ui@next
|
yarn add @nuxt/ui
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash [npm]
|
```bash [npm]
|
||||||
npm install @nuxt/ui@next
|
npm install @nuxt/ui
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash [bun]
|
```bash [bun]
|
||||||
bun add @nuxt/ui@next
|
bun add @nuxt/ui
|
||||||
```
|
```
|
||||||
|
|
||||||
::::
|
::::
|
||||||
@@ -81,19 +81,19 @@ bun add @nuxt/ui@next
|
|||||||
::::code-group{sync="pm"}
|
::::code-group{sync="pm"}
|
||||||
|
|
||||||
```bash [pnpm]
|
```bash [pnpm]
|
||||||
pnpm add @nuxt/ui-pro@next
|
pnpm add @nuxt/ui-pro
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash [yarn]
|
```bash [yarn]
|
||||||
yarn add @nuxt/ui-pro@next
|
yarn add @nuxt/ui-pro
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash [npm]
|
```bash [npm]
|
||||||
npm install @nuxt/ui-pro@next
|
npm install @nuxt/ui-pro
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash [bun]
|
```bash [bun]
|
||||||
bun add @nuxt/ui-pro@next
|
bun add @nuxt/ui-pro
|
||||||
```
|
```
|
||||||
|
|
||||||
::::
|
::::
|
||||||
@@ -130,7 +130,7 @@ bun add @nuxt/ui-pro@next
|
|||||||
#ui
|
#ui
|
||||||
|
|
||||||
:::div
|
:::div
|
||||||
5. Wrap you app with the [App](/components/app) component:
|
5. Wrap your app with the [App](/components/app) component:
|
||||||
:::
|
:::
|
||||||
|
|
||||||
#ui-pro
|
#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" />
|
+ <USelect :items="countries" />
|
||||||
|
|
||||||
- <UHorizontalNavigation :links="links" />
|
- <UHorizontalNavigation :links="links" />
|
||||||
+ <UNavigationMenu :items="items" />
|
+ <UNavigationMenu :items="links" />
|
||||||
</template>
|
</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`.
|
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
|
::warning
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ navigation.icon: i-lucide-swatch-book
|
|||||||
|
|
||||||
## Tailwind CSS
|
## 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`
|
### `@theme`
|
||||||
|
|
||||||
@@ -142,7 +142,7 @@ Nuxt UI leverages Vite config to provide customizable color aliases based on [Ta
|
|||||||
|
|
||||||
::framework-only
|
::framework-only
|
||||||
#nuxt
|
#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:
|
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]
|
```ts [app.config.ts]
|
||||||
@@ -156,14 +156,19 @@ export default defineAppConfig({
|
|||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
::
|
:::
|
||||||
|
|
||||||
#vue
|
#vue
|
||||||
::module-only
|
|
||||||
#ui
|
|
||||||
:::div
|
:::div
|
||||||
You can configure these color aliases at runtime in your `vite.config.ts` file under the `ui.colors` key:
|
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]
|
```ts [vite.config.ts]
|
||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from 'vite'
|
||||||
import vue from '@vitejs/plugin-vue'
|
import vue from '@vitejs/plugin-vue'
|
||||||
@@ -183,11 +188,12 @@ export default defineConfig({
|
|||||||
]
|
]
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
:::
|
:::::
|
||||||
|
|
||||||
#ui-pro
|
#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]
|
```ts [vite.config.ts]
|
||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from 'vite'
|
||||||
import vue from '@vitejs/plugin-vue'
|
import vue from '@vitejs/plugin-vue'
|
||||||
@@ -207,9 +213,12 @@ export default defineConfig({
|
|||||||
]
|
]
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
:::
|
|
||||||
|
|
||||||
::
|
:::::
|
||||||
|
|
||||||
|
::::
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
@@ -256,11 +265,17 @@ export default defineNuxtConfig({
|
|||||||
:::
|
:::
|
||||||
|
|
||||||
#vue
|
#vue
|
||||||
::module-only
|
|
||||||
#ui
|
|
||||||
:::tip
|
:::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.
|
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]
|
```ts [vite.config.ts]
|
||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from 'vite'
|
||||||
import vue from '@vitejs/plugin-vue'
|
import vue from '@vitejs/plugin-vue'
|
||||||
@@ -283,11 +298,11 @@ export default defineConfig({
|
|||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
:::
|
:::::
|
||||||
|
|
||||||
#ui-pro
|
#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]
|
```ts [vite.config.ts]
|
||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from 'vite'
|
||||||
@@ -311,9 +326,12 @@ export default defineConfig({
|
|||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
|
:::::
|
||||||
|
|
||||||
|
::::
|
||||||
|
|
||||||
:::
|
:::
|
||||||
|
|
||||||
::
|
|
||||||
::
|
::
|
||||||
|
|
||||||
### Tokens
|
### Tokens
|
||||||
|
|||||||
@@ -17,6 +17,11 @@ Nuxt UI provides an **App** component that wraps your app to provide global conf
|
|||||||
|
|
||||||
### Locale
|
### Locale
|
||||||
|
|
||||||
|
::module-only
|
||||||
|
|
||||||
|
#ui
|
||||||
|
:::div
|
||||||
|
|
||||||
Use the `locale` prop with the locale you want to use from `@nuxt/ui/locale`:
|
Use the `locale` prop with the locale you want to use from `@nuxt/ui/locale`:
|
||||||
|
|
||||||
```vue [app.vue]
|
```vue [app.vue]
|
||||||
@@ -31,13 +36,42 @@ import { fr } from '@nuxt/ui/locale'
|
|||||||
</template>
|
</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
|
### Custom locale
|
||||||
|
|
||||||
You also have the option to add your own locale using `defineLocale`:
|
You also have the option to add your own locale using `defineLocale`:
|
||||||
|
|
||||||
|
::module-only
|
||||||
|
|
||||||
|
#ui
|
||||||
|
:::div
|
||||||
|
|
||||||
```vue [app.vue]
|
```vue [app.vue]
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const locale = defineLocale({
|
import type { Messages } from '@nuxt/ui'
|
||||||
|
|
||||||
|
const locale = defineLocale<Messages>({
|
||||||
name: 'My custom locale',
|
name: 'My custom locale',
|
||||||
code: 'en',
|
code: 'en',
|
||||||
dir: 'ltr',
|
dir: 'ltr',
|
||||||
@@ -54,6 +88,35 @@ const locale = defineLocale({
|
|||||||
</template>
|
</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
|
::tip
|
||||||
Look at the `code` parameter, there you need to pass the iso code of the language. Example:
|
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`
|
#### Set the `locale` prop using `useI18n`
|
||||||
|
|
||||||
|
::module-only
|
||||||
|
|
||||||
|
#ui
|
||||||
|
:::div
|
||||||
|
|
||||||
```vue [app.vue]
|
```vue [app.vue]
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import * as locales from '@nuxt/ui/locale'
|
import * as locales from '@nuxt/ui/locale'
|
||||||
@@ -130,6 +198,28 @@ const { locale } = useI18n()
|
|||||||
</template>
|
</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
|
### 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:
|
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]
|
```vue [app.vue]
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import * as locales from '@nuxt/ui/locale'
|
import * as locales from '@nuxt/ui/locale'
|
||||||
@@ -162,6 +257,38 @@ useHead({
|
|||||||
</template>
|
</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
|
||||||
|
|
||||||
:supported-languages
|
:supported-languages
|
||||||
|
|||||||
@@ -17,6 +17,11 @@ Nuxt UI provides an **App** component that wraps your app to provide global conf
|
|||||||
|
|
||||||
### Locale
|
### Locale
|
||||||
|
|
||||||
|
::module-only
|
||||||
|
|
||||||
|
#ui
|
||||||
|
:::div
|
||||||
|
|
||||||
Use the `locale` prop with the locale you want to use from `@nuxt/ui/locale`:
|
Use the `locale` prop with the locale you want to use from `@nuxt/ui/locale`:
|
||||||
|
|
||||||
```vue [App.vue]
|
```vue [App.vue]
|
||||||
@@ -31,15 +36,43 @@ import { fr } from '@nuxt/ui/locale'
|
|||||||
</template>
|
</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
|
### Custom locale
|
||||||
|
|
||||||
You also have the option to add your locale using `defineLocale`:
|
You also have the option to add your locale using `defineLocale`:
|
||||||
|
|
||||||
|
::module-only
|
||||||
|
|
||||||
|
#ui
|
||||||
|
:::div
|
||||||
|
|
||||||
```vue [App.vue]
|
```vue [App.vue]
|
||||||
<script setup lang="ts">
|
<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',
|
name: 'My custom locale',
|
||||||
code: 'en',
|
code: 'en',
|
||||||
dir: 'ltr',
|
dir: 'ltr',
|
||||||
@@ -56,6 +89,36 @@ const locale = defineLocale({
|
|||||||
</template>
|
</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
|
::tip
|
||||||
Look at the `code` parameter, there you need to pass the iso code of the language. Example:
|
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`
|
#### Set the `locale` prop using `useI18n`
|
||||||
|
|
||||||
|
::module-only
|
||||||
|
|
||||||
|
#ui
|
||||||
|
:::div
|
||||||
|
|
||||||
```vue [App.vue]
|
```vue [App.vue]
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
@@ -146,6 +214,29 @@ const { locale } = useI18n()
|
|||||||
</template>
|
</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
|
### 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:
|
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]
|
```vue [App.vue]
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
@@ -181,6 +277,41 @@ useHead({
|
|||||||
</template>
|
</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
|
||||||
|
|
||||||
:supported-languages
|
:supported-languages
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
---
|
---
|
||||||
title: Contribution Guide
|
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
|
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.
|
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
|
::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
|
## 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
|
### Color
|
||||||
|
|
||||||
Use the `color` prop to change the color of the Button.
|
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
|
### Loading
|
||||||
|
|
||||||
Use the `loading` prop to show a loading icon and disable the Button.
|
Use the `loading` prop to show a loading icon and disable the Button.
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ links:
|
|||||||
|
|
||||||
## Usage
|
## 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.
|
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:
|
It requires two props:
|
||||||
|
|
||||||
- `state` - a reactive object holding the form's state.
|
- `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
|
::warning
|
||||||
**No validation library is included** by default, ensure you **install the one you need**.
|
**No validation library is included** by default, ensure you **install the one you need**.
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ links:
|
|||||||
- label: GitHub
|
- label: GitHub
|
||||||
icon: i-simple-icons-github
|
icon: i-simple-icons-github
|
||||||
to: https://github.com/nuxt/ui/tree/v3/src/runtime/components/Tree.vue
|
to: https://github.com/nuxt/ui/tree/v3/src/runtime/components/Tree.vue
|
||||||
navigation.badge: New
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ export default defineNuxtConfig({
|
|||||||
},
|
},
|
||||||
$production: {
|
$production: {
|
||||||
site: {
|
site: {
|
||||||
url: 'https://ui3.nuxt.dev'
|
url: 'https://ui.nuxt.com'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -55,6 +55,7 @@ export default defineNuxtConfig({
|
|||||||
}]
|
}]
|
||||||
},
|
},
|
||||||
rootAttrs: {
|
rootAttrs: {
|
||||||
|
// @ts-expect-error - vaul-drawer-wrapper is not typed
|
||||||
'vaul-drawer-wrapper': '',
|
'vaul-drawer-wrapper': '',
|
||||||
'class': 'bg-(--ui-bg)'
|
'class': 'bg-(--ui-bg)'
|
||||||
}
|
}
|
||||||
@@ -85,8 +86,8 @@ export default defineNuxtConfig({
|
|||||||
},
|
},
|
||||||
|
|
||||||
routeRules: {
|
routeRules: {
|
||||||
'/': { redirect: '/getting-started', prerender: false },
|
|
||||||
'/getting-started/installation': { redirect: '/getting-started/installation/nuxt', 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/icons': { redirect: '/getting-started/icons/nuxt', prerender: false },
|
||||||
'/getting-started/color-mode': { redirect: '/getting-started/color-mode/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 },
|
'/getting-started/i18n': { redirect: '/getting-started/i18n/nuxt', prerender: false },
|
||||||
@@ -104,9 +105,10 @@ export default defineNuxtConfig({
|
|||||||
routes: [
|
routes: [
|
||||||
'/getting-started',
|
'/getting-started',
|
||||||
'/api/countries.json',
|
'/api/countries.json',
|
||||||
'/api/locales.json'
|
'/api/locales.json',
|
||||||
// '/api/releases.json',
|
// '/api/releases.json',
|
||||||
// '/api/pulls.json'
|
// '/api/pulls.json'
|
||||||
|
'/404.html'
|
||||||
],
|
],
|
||||||
crawlLinks: true,
|
crawlLinks: true,
|
||||||
autoSubfolderIndex: false
|
autoSubfolderIndex: false
|
||||||
@@ -127,7 +129,12 @@ export default defineNuxtConfig({
|
|||||||
vite: {
|
vite: {
|
||||||
plugins: [
|
plugins: [
|
||||||
yaml()
|
yaml()
|
||||||
]
|
],
|
||||||
|
server: {
|
||||||
|
fs: {
|
||||||
|
allow: process.env.NUXT_UI_PRO_PATH ? [resolve(process.env.NUXT_UI_PRO_PATH)] : undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
componentMeta: {
|
componentMeta: {
|
||||||
@@ -169,12 +176,12 @@ export default defineNuxtConfig({
|
|||||||
},
|
},
|
||||||
|
|
||||||
llms: {
|
llms: {
|
||||||
domain: 'https://ui3.nuxt.dev',
|
domain: 'https://ui.nuxt.com',
|
||||||
title: 'Nuxt UI v3',
|
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.',
|
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: {
|
full: {
|
||||||
title: 'Nuxt UI v3 Full Documentation',
|
title: 'Nuxt UI Full Documentation',
|
||||||
description: 'This is the full documentation for Nuxt UI v3. It includes all the Markdown files written with the MDC syntax.'
|
description: 'This is the full documentation for Nuxt UI. It includes all the Markdown files written with the MDC syntax.'
|
||||||
},
|
},
|
||||||
sections: [
|
sections: [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,34 +4,34 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@iconify-json/logos": "^1.2.4",
|
"@iconify-json/logos": "^1.2.4",
|
||||||
"@iconify-json/lucide": "^1.2.28",
|
"@iconify-json/lucide": "^1.2.30",
|
||||||
"@iconify-json/simple-icons": "^1.2.27",
|
"@iconify-json/simple-icons": "^1.2.28",
|
||||||
"@iconify-json/vscode-icons": "^1.2.16",
|
"@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/image": "^1.9.0",
|
||||||
"@nuxt/ui": "latest",
|
"@nuxt/ui": "latest",
|
||||||
"@nuxt/ui-pro": "https://pkg.pr.new/@nuxt/ui-pro@7cf4b69",
|
"@nuxt/ui-pro": "https://pkg.pr.new/@nuxt/ui-pro@d96a086",
|
||||||
"@nuxthub/core": "^0.8.17",
|
"@nuxthub/core": "^0.8.18",
|
||||||
"@nuxtjs/plausible": "^1.2.0",
|
"@nuxtjs/plausible": "^1.2.0",
|
||||||
"@octokit/rest": "^21.1.1",
|
"@octokit/rest": "^21.1.1",
|
||||||
"@rollup/plugin-yaml": "^4.1.2",
|
"@rollup/plugin-yaml": "^4.1.2",
|
||||||
"@vueuse/nuxt": "^12.7.0",
|
"@vueuse/nuxt": "^13.0.0",
|
||||||
"joi": "^17.13.3",
|
"joi": "^17.13.3",
|
||||||
"motion": "^12.4.7",
|
"motion": "^12.5.0",
|
||||||
"motion-v": "0.11.0-beta.6",
|
"motion-v": "0.13.0",
|
||||||
"nuxt": "^3.15.4",
|
"nuxt": "^3.16.0",
|
||||||
"nuxt-component-meta": "^0.10.0",
|
"nuxt-component-meta": "^0.10.0",
|
||||||
"nuxt-llms": "^0.1.0",
|
"nuxt-llms": "^0.1.0",
|
||||||
"nuxt-og-image": "^4.1.5",
|
"nuxt-og-image": "^5.0.4",
|
||||||
"prettier": "^3.5.3",
|
"prettier": "^3.5.3",
|
||||||
"shiki-transformer-color-highlight": "^0.2.0",
|
"shiki-transformer-color-highlight": "^1.0.0",
|
||||||
"superstruct": "^2.0.2",
|
"superstruct": "^2.0.2",
|
||||||
"ufo": "^1.5.4",
|
"ufo": "^1.5.4",
|
||||||
"valibot": "^0.42.1",
|
"valibot": "^1.0.0",
|
||||||
"yup": "^1.6.1",
|
"yup": "^1.6.1",
|
||||||
"zod": "^3.24.2"
|
"zod": "^3.24.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"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",
|
"name": "@nuxt/ui",
|
||||||
"description": "A UI Library for Modern Web Apps, powered by Vue & Tailwind CSS.",
|
"description": "A UI Library for Modern Web Apps, powered by Vue & Tailwind CSS.",
|
||||||
"version": "3.0.0-beta.2",
|
"version": "3.0.0",
|
||||||
"packageManager": "pnpm@10.5.2",
|
"packageManager": "pnpm@10.6.4",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/nuxt/ui.git"
|
"url": "git+https://github.com/nuxt/ui.git"
|
||||||
},
|
},
|
||||||
"homepage": "https://ui3.nuxt.dev",
|
"homepage": "https://ui.nuxt.com",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"exports": {
|
"exports": {
|
||||||
@@ -67,7 +67,7 @@
|
|||||||
"dev:build": "nuxi build playground",
|
"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",
|
"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": "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",
|
"docs:prepare": "nuxt-component-meta docs",
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"lint:fix": "eslint . --fix",
|
"lint:fix": "eslint . --fix",
|
||||||
@@ -75,23 +75,23 @@
|
|||||||
"test": "vitest",
|
"test": "vitest",
|
||||||
"test:vue": "vitest -c vitest.vue.config.ts",
|
"test:vue": "vitest -c vitest.vue.config.ts",
|
||||||
"test:vue:build": "vite build playground-vue",
|
"test:vue:build": "vite build playground-vue",
|
||||||
"release": "release-it --preRelease=beta --npm.tag=next"
|
"release": "release-it"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@iconify/vue": "^4.3.0",
|
"@iconify/vue": "^4.3.0",
|
||||||
"@internationalized/date": "^3.7.0",
|
"@internationalized/date": "^3.7.0",
|
||||||
"@internationalized/number": "^3.6.0",
|
"@internationalized/number": "^3.6.0",
|
||||||
"@nuxt/fonts": "^0.10.3",
|
"@nuxt/fonts": "^0.11.0",
|
||||||
"@nuxt/icon": "^1.10.3",
|
"@nuxt/icon": "^1.11.0",
|
||||||
"@nuxt/kit": "^3.15.4",
|
"@nuxt/kit": "^3.16.0",
|
||||||
"@nuxt/schema": "^3.15.4",
|
"@nuxt/schema": "^3.16.0",
|
||||||
"@nuxtjs/color-mode": "^3.5.2",
|
"@nuxtjs/color-mode": "^3.5.2",
|
||||||
"@tailwindcss/postcss": "^4.0.9",
|
"@tailwindcss/postcss": "^4.0.14",
|
||||||
"@tailwindcss/vite": "^4.0.9",
|
"@tailwindcss/vite": "^4.0.14",
|
||||||
"@tanstack/vue-table": "^8.21.2",
|
"@tanstack/vue-table": "^8.21.2",
|
||||||
"@unhead/vue": "^1.11.20",
|
"@unhead/vue": "^2.0.0-rc.13",
|
||||||
"@vueuse/core": "^12.7.0",
|
"@vueuse/core": "^13.0.0",
|
||||||
"@vueuse/integrations": "^12.7.0",
|
"@vueuse/integrations": "^13.0.0",
|
||||||
"colortranslator": "^4.1.0",
|
"colortranslator": "^4.1.0",
|
||||||
"consola": "^3.4.0",
|
"consola": "^3.4.0",
|
||||||
"defu": "^6.1.4",
|
"defu": "^6.1.4",
|
||||||
@@ -106,34 +106,36 @@
|
|||||||
"knitwork": "^1.2.0",
|
"knitwork": "^1.2.0",
|
||||||
"magic-string": "^0.30.17",
|
"magic-string": "^0.30.17",
|
||||||
"mlly": "^1.7.4",
|
"mlly": "^1.7.4",
|
||||||
"ohash": "^1.1.5",
|
"ohash": "^2.0.11",
|
||||||
"pathe": "^2.0.3",
|
"pathe": "^2.0.3",
|
||||||
"reka-ui": "^2.0.2",
|
"reka-ui": "^2.1.0",
|
||||||
"scule": "^1.3.0",
|
"scule": "^1.3.0",
|
||||||
"tailwind-variants": "^0.3.1",
|
"tailwind-variants": "^1.0.0",
|
||||||
"tailwindcss": "^4.0.9",
|
"tailwindcss": "^4.0.14",
|
||||||
"tinyglobby": "^0.2.12",
|
"tinyglobby": "^0.2.12",
|
||||||
"unplugin": "^2.2.0",
|
"unplugin": "^2.2.0",
|
||||||
"unplugin-auto-import": "^19.1.1",
|
"unplugin-auto-import": "^19.1.1",
|
||||||
"unplugin-vue-components": "^28.4.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": {
|
"devDependencies": {
|
||||||
"@nuxt/eslint-config": "^1.1.0",
|
"@nuxt/eslint-config": "^1.2.0",
|
||||||
"@nuxt/module-builder": "^0.8.4",
|
"@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",
|
"@release-it/conventional-changelog": "^10.0.0",
|
||||||
"@standard-schema/spec": "^1.0.0",
|
"@standard-schema/spec": "^1.0.0",
|
||||||
"@vue/test-utils": "^2.4.6",
|
"@vue/test-utils": "^2.4.6",
|
||||||
"embla-carousel": "^8.5.2",
|
"embla-carousel": "^8.5.2",
|
||||||
"eslint": "^9.21.0",
|
"eslint": "^9.22.0",
|
||||||
"happy-dom": "^17.1.2",
|
"happy-dom": "^17.4.4",
|
||||||
"joi": "^17.13.3",
|
"joi": "^17.13.3",
|
||||||
"nuxt": "^3.15.4",
|
"nuxt": "^3.16.0",
|
||||||
"release-it": "^18.1.2",
|
"release-it": "^18.1.2",
|
||||||
"superstruct": "^2.0.2",
|
"superstruct": "^2.0.2",
|
||||||
"valibot": "^0.42.1",
|
"valibot": "^1.0.0",
|
||||||
"vitest": "^3.0.7",
|
"vitest": "^3.0.9",
|
||||||
"vitest-environment-nuxt": "^1.0.1",
|
"vitest-environment-nuxt": "^1.0.1",
|
||||||
"vue-tsc": "^2.2.0",
|
"vue-tsc": "^2.2.0",
|
||||||
"yup": "^1.6.1",
|
"yup": "^1.6.1",
|
||||||
@@ -146,12 +148,9 @@
|
|||||||
"@nuxt/ui": "workspace:*",
|
"@nuxt/ui": "workspace:*",
|
||||||
"chokidar": "3.6.0",
|
"chokidar": "3.6.0",
|
||||||
"debug": "4.3.7",
|
"debug": "4.3.7",
|
||||||
"happy-dom": "17.1.2",
|
"rollup": "4.34.9",
|
||||||
"rollup": "4.32.1",
|
|
||||||
"typescript": "5.6.3",
|
"typescript": "5.6.3",
|
||||||
"unimport": "3.14.5",
|
|
||||||
"unplugin": "^2.2.0",
|
"unplugin": "^2.2.0",
|
||||||
"vue": "3.5.13",
|
|
||||||
"vue-tsc": "2.2.0"
|
"vue-tsc": "2.2.0"
|
||||||
},
|
},
|
||||||
"pnpm": {
|
"pnpm": {
|
||||||
|
|||||||
@@ -2,8 +2,9 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<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" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Nuxt UI ❤️ Vue</title>
|
<title>Nuxt UI - Vue Playground</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app" class="isolate"></div>
|
<div id="app" class="isolate"></div>
|
||||||
|
|||||||
@@ -15,9 +15,9 @@
|
|||||||
"vue-router": "^4.5.0"
|
"vue-router": "^4.5.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vitejs/plugin-vue": "^5.2.1",
|
"@vitejs/plugin-vue": "^5.2.3",
|
||||||
"typescript": "^5.6.3",
|
"typescript": "^5.6.3",
|
||||||
"vite": "^6.2.0",
|
"vite": "^6.2.2",
|
||||||
"vue-tsc": "^2.2.0"
|
"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">
|
<script setup lang="ts">
|
||||||
import { h, resolveComponent } from 'vue'
|
import { h, resolveComponent } from 'vue'
|
||||||
import { upperFirst } from 'scule'
|
import { upperFirst } from 'scule'
|
||||||
import type { TableColumn } from '@nuxt/ui'
|
import type { TableColumn, TableRow } from '@nuxt/ui'
|
||||||
import { getPaginationRowModel } from '@tanstack/vue-table'
|
import { getPaginationRowModel } from '@tanstack/vue-table'
|
||||||
|
|
||||||
const UButton = resolveComponent('UButton')
|
const UButton = resolveComponent('UButton')
|
||||||
@@ -279,6 +279,10 @@ function randomize() {
|
|||||||
data.value = [...data.value].sort(() => Math.random() - 0.5)
|
data.value = [...data.value].sort(() => Math.random() - 0.5)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onSelect(row: TableRow<Payment>) {
|
||||||
|
console.log(row)
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
@@ -337,6 +341,7 @@ onMounted(() => {
|
|||||||
}"
|
}"
|
||||||
sticky
|
sticky
|
||||||
class="border border-(--ui-border-accented) rounded-(--ui-radius)"
|
class="border border-(--ui-border-accented) rounded-(--ui-radius)"
|
||||||
|
@select="onSelect"
|
||||||
>
|
>
|
||||||
<template #expanded="{ row }">
|
<template #expanded="{ row }">
|
||||||
<pre>{{ row.original }}</pre>
|
<pre>{{ row.original }}</pre>
|
||||||
|
|||||||
@@ -8,10 +8,10 @@
|
|||||||
"generate": "nuxi generate"
|
"generate": "nuxi generate"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@iconify-json/lucide": "^1.2.28",
|
"@iconify-json/lucide": "^1.2.30",
|
||||||
"@iconify-json/simple-icons": "^1.2.27",
|
"@iconify-json/simple-icons": "^1.2.28",
|
||||||
"@nuxt/ui": "latest",
|
"@nuxt/ui": "latest",
|
||||||
"@nuxthub/core": "^0.8.17",
|
"@nuxthub/core": "^0.8.18",
|
||||||
"nuxt": "^3.15.4"
|
"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
|
"enabled": true
|
||||||
},
|
},
|
||||||
"ignoreDeps": [
|
"ignoreDeps": [
|
||||||
"happy-dom",
|
|
||||||
"valibot30",
|
|
||||||
"valibot31",
|
|
||||||
"typescript",
|
"typescript",
|
||||||
|
"vaul-vue",
|
||||||
"vue-tsc"
|
"vue-tsc"
|
||||||
],
|
],
|
||||||
"baseBranches": ["dev", "v3"],
|
"baseBranches": ["v2", "v3"],
|
||||||
"packageRules": [{
|
"packageRules": [{
|
||||||
"matchBaseBranches": ["v3"],
|
"matchBaseBranches": ["v3"],
|
||||||
"labels": ["v3"]
|
"labels": ["v3"]
|
||||||
|
|||||||
@@ -9,40 +9,40 @@ export interface ModuleOptions {
|
|||||||
/**
|
/**
|
||||||
* Prefix for components
|
* Prefix for components
|
||||||
* @defaultValue `U`
|
* @defaultValue `U`
|
||||||
* @link https://ui3.nuxt.dev/getting-started/installation/nuxt#prefix
|
* @link https://ui.nuxt.com/getting-started/installation/nuxt#prefix
|
||||||
*/
|
*/
|
||||||
prefix?: string
|
prefix?: string
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enable or disable `@nuxt/fonts` module
|
* Enable or disable `@nuxt/fonts` module
|
||||||
* @defaultValue `true`
|
* @defaultValue `true`
|
||||||
* @link https://ui3.nuxt.dev/getting-started/installation/nuxt#fonts
|
* @link https://ui.nuxt.com/getting-started/installation/nuxt#fonts
|
||||||
*/
|
*/
|
||||||
fonts?: boolean
|
fonts?: boolean
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enable or disable `@nuxtjs/color-mode` module
|
* Enable or disable `@nuxtjs/color-mode` module
|
||||||
* @defaultValue `true`
|
* @defaultValue `true`
|
||||||
* @link https://ui3.nuxt.dev/getting-started/installation/nuxt#colormode
|
* @link https://ui.nuxt.com/getting-started/installation/nuxt#colormode
|
||||||
*/
|
*/
|
||||||
colorMode?: boolean
|
colorMode?: boolean
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Customize how the theme is generated
|
* Customize how the theme is generated
|
||||||
* @link https://ui3.nuxt.dev/getting-started/theme
|
* @link https://ui.nuxt.com/getting-started/theme
|
||||||
*/
|
*/
|
||||||
theme?: {
|
theme?: {
|
||||||
/**
|
/**
|
||||||
* Define the color aliases available for components
|
* Define the color aliases available for components
|
||||||
* @defaultValue `['primary', 'secondary', 'success', 'info', 'warning', 'error']`
|
* @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[]
|
colors?: string[]
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enable or disable transitions on components
|
* Enable or disable transitions on components
|
||||||
* @defaultValue `true`
|
* @defaultValue `true`
|
||||||
* @link https://ui3.nuxt.dev/getting-started/installation/nuxt#themetransitions
|
* @link https://ui.nuxt.com/getting-started/installation/nuxt#themetransitions
|
||||||
*/
|
*/
|
||||||
transitions?: boolean
|
transitions?: boolean
|
||||||
}
|
}
|
||||||
@@ -53,9 +53,9 @@ export default defineNuxtModule<ModuleOptions>({
|
|||||||
name: 'ui',
|
name: 'ui',
|
||||||
configKey: 'ui',
|
configKey: 'ui',
|
||||||
compatibility: {
|
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,
|
defaults: defaultOptions,
|
||||||
async setup(options, nuxt) {
|
async setup(options, nuxt) {
|
||||||
|
|||||||
@@ -20,7 +20,11 @@ export default function ComponentImportPlugin(options: NuxtUIOptions & { prefix:
|
|||||||
|
|
||||||
const pluginOptions = defu(options.components, <ComponentsOptions>{
|
const pluginOptions = defu(options.components, <ComponentsOptions>{
|
||||||
dts: options.dts ?? true,
|
dts: options.dts ?? true,
|
||||||
exclude: [/[\\/]node_modules[\\/](?!\.pnpm|@nuxt\/ui)/, /[\\/]\.git[\\/]/, /[\\/]\.nuxt[\\/]/],
|
exclude: [
|
||||||
|
/[\\/]node_modules[\\/](?!\.pnpm|@nuxt\/ui|@compodium\/examples)/,
|
||||||
|
/[\\/]\.git[\\/]/,
|
||||||
|
/[\\/]\.nuxt[\\/]/
|
||||||
|
],
|
||||||
resolvers: [
|
resolvers: [
|
||||||
(componentName) => {
|
(componentName) => {
|
||||||
if (overrideNames.has(componentName))
|
if (overrideNames.has(componentName))
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
<!-- eslint-disable vue/block-tag-newline -->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { AccordionRootProps, AccordionRootEmits } from 'reka-ui'
|
import type { AccordionRootProps, AccordionRootEmits } from 'reka-ui'
|
||||||
import type { AppConfig } from '@nuxt/schema'
|
import type { AppConfig } from '@nuxt/schema'
|
||||||
@@ -60,6 +61,7 @@ export type AccordionSlots<T extends { slot?: string }> = {
|
|||||||
content: SlotProps<T>
|
content: SlotProps<T>
|
||||||
body: SlotProps<T>
|
body: SlotProps<T>
|
||||||
} & DynamicSlots<T, SlotProps<T>>
|
} & DynamicSlots<T, SlotProps<T>>
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script setup lang="ts" generic="T extends AccordionItem">
|
<script setup lang="ts" generic="T extends AccordionItem">
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { ConfigProviderProps, TooltipProviderProps } from 'reka-ui'
|
import type { ConfigProviderProps, TooltipProviderProps } from 'reka-ui'
|
||||||
import { localeContextInjectionKey } from '../composables/useLocale'
|
import type { ToasterProps, Locale, Messages } from '../types'
|
||||||
import type { ToasterProps, Locale } 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
|
tooltip?: TooltipProviderProps
|
||||||
toaster?: ToasterProps | null
|
toaster?: ToasterProps | null
|
||||||
locale?: Locale
|
locale?: Locale<T>
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AppSlots {
|
export interface AppSlots {
|
||||||
@@ -18,14 +17,15 @@ export default {
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts" generic="T extends Messages = Messages">
|
||||||
import { toRef, useId, provide } from 'vue'
|
import { toRef, useId, provide } from 'vue'
|
||||||
import { ConfigProvider, TooltipProvider, useForwardProps } from 'reka-ui'
|
import { ConfigProvider, TooltipProvider, useForwardProps } from 'reka-ui'
|
||||||
import { reactivePick } from '@vueuse/core'
|
import { reactivePick } from '@vueuse/core'
|
||||||
|
import { localeContextInjectionKey } from '../composables/useLocale'
|
||||||
import UToaster from './Toaster.vue'
|
import UToaster from './Toaster.vue'
|
||||||
import UOverlayProvider from './OverlayProvider.vue'
|
import UOverlayProvider from './OverlayProvider.vue'
|
||||||
|
|
||||||
const props = defineProps<AppProps>()
|
const props = defineProps<AppProps<T>>()
|
||||||
defineSlots<AppSlots>()
|
defineSlots<AppSlots>()
|
||||||
|
|
||||||
const configProviderProps = useForwardProps(reactivePick(props, 'scrollBody'))
|
const configProviderProps = useForwardProps(reactivePick(props, 'scrollBody'))
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ export interface AvatarProps {
|
|||||||
*/
|
*/
|
||||||
size?: AvatarVariants['size']
|
size?: AvatarVariants['size']
|
||||||
class?: any
|
class?: any
|
||||||
|
style?: any
|
||||||
ui?: Partial<typeof avatar.slots>
|
ui?: Partial<typeof avatar.slots>
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,7 +84,7 @@ function onError() {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<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
|
<component
|
||||||
:is="ImageComponent"
|
:is="ImageComponent"
|
||||||
v-if="src && !error"
|
v-if="src && !error"
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
<!-- eslint-disable vue/block-tag-newline -->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { AppConfig } from '@nuxt/schema'
|
import type { AppConfig } from '@nuxt/schema'
|
||||||
import _appConfig from '#build/app.config'
|
import _appConfig from '#build/app.config'
|
||||||
@@ -51,6 +52,7 @@ export type BreadcrumbSlots<T extends { slot?: string }> = {
|
|||||||
'item-trailing': SlotProps<T>
|
'item-trailing': SlotProps<T>
|
||||||
'separator'(props?: {}): any
|
'separator'(props?: {}): any
|
||||||
} & DynamicSlots<T, SlotProps<T>>
|
} & DynamicSlots<T, SlotProps<T>>
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script setup lang="ts" generic="T extends BreadcrumbItem">
|
<script setup lang="ts" generic="T extends BreadcrumbItem">
|
||||||
|
|||||||
@@ -21,10 +21,12 @@ export interface ButtonProps extends UseComponentIconsProps, Omit<LinkProps, 'ra
|
|||||||
* @defaultValue 'primary'
|
* @defaultValue 'primary'
|
||||||
*/
|
*/
|
||||||
color?: ButtonVariants['color']
|
color?: ButtonVariants['color']
|
||||||
|
activeColor?: ButtonVariants['color']
|
||||||
/**
|
/**
|
||||||
* @defaultValue 'solid'
|
* @defaultValue 'solid'
|
||||||
*/
|
*/
|
||||||
variant?: ButtonVariants['variant']
|
variant?: ButtonVariants['variant']
|
||||||
|
activeVariant?: ButtonVariants['variant']
|
||||||
/**
|
/**
|
||||||
* @defaultValue 'md'
|
* @defaultValue 'md'
|
||||||
*/
|
*/
|
||||||
@@ -58,8 +60,13 @@ import { pickLinkProps } from '../utils/link'
|
|||||||
import UIcon from './Icon.vue'
|
import UIcon from './Icon.vue'
|
||||||
import UAvatar from './Avatar.vue'
|
import UAvatar from './Avatar.vue'
|
||||||
import ULink from './Link.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 slots = defineSlots<ButtonSlots>()
|
||||||
|
|
||||||
const linkProps = useForwardProps(pickLinkProps(props))
|
const linkProps = useForwardProps(pickLinkProps(props))
|
||||||
@@ -87,7 +94,19 @@ const { isLeading, isTrailing, leadingIconName, trailingIconName } = useComponen
|
|||||||
computed(() => ({ ...props, loading: isLoading.value }))
|
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,
|
color: props.color,
|
||||||
variant: props.variant,
|
variant: props.variant,
|
||||||
size: buttonSize.value,
|
size: buttonSize.value,
|
||||||
@@ -102,26 +121,37 @@ const ui = computed(() => button({
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<ULink
|
<ULink
|
||||||
|
v-slot="{ active, ...slotProps }"
|
||||||
:type="type"
|
:type="type"
|
||||||
:disabled="disabled || isLoading"
|
:disabled="disabled || isLoading"
|
||||||
:class="ui.base({ class: [props.class, props.ui?.base] })"
|
:class="ui.base({ class: [props.class, props.ui?.base] })"
|
||||||
v-bind="omit(linkProps, ['type', 'disabled'])"
|
v-bind="omit(linkProps, ['type', 'disabled'])"
|
||||||
raw
|
custom
|
||||||
@click="onClickWrapper"
|
|
||||||
>
|
>
|
||||||
<slot name="leading">
|
<ULinkBase
|
||||||
<UIcon v-if="isLeading && leadingIconName" :name="leadingIconName" :class="ui.leadingIcon({ class: props.ui?.leadingIcon })" />
|
v-bind="slotProps"
|
||||||
<UAvatar v-else-if="!!avatar" :size="((props.ui?.leadingAvatarSize || ui.leadingAvatarSize()) as AvatarProps['size'])" v-bind="avatar" :class="ui.leadingAvatar({ class: props.ui?.leadingAvatar })" />
|
:class="ui.base({
|
||||||
</slot>
|
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>
|
<slot>
|
||||||
<span v-if="label" :class="ui.label({ class: props.ui?.label })">
|
<span v-if="label" :class="ui.label({ class: props.ui?.label, active })">
|
||||||
{{ label }}
|
{{ label }}
|
||||||
</span>
|
</span>
|
||||||
</slot>
|
</slot>
|
||||||
|
|
||||||
<slot name="trailing">
|
<slot name="trailing">
|
||||||
<UIcon v-if="isTrailing && trailingIconName" :name="trailingIconName" :class="ui.trailingIcon({ class: props.ui?.trailingIcon })" />
|
<UIcon v-if="isTrailing && trailingIconName" :name="trailingIconName" :class="ui.trailingIcon({ class: props.ui?.trailingIcon, active })" />
|
||||||
</slot>
|
</slot>
|
||||||
|
</ULinkBase>
|
||||||
</ULink>
|
</ULink>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
<!-- eslint-disable vue/block-tag-newline -->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { VariantProps } from 'tailwind-variants'
|
import type { VariantProps } from 'tailwind-variants'
|
||||||
import type { MaybeRefOrGetter } from '@vueuse/shared'
|
import type { MaybeRefOrGetter } from '@vueuse/shared'
|
||||||
@@ -70,6 +71,7 @@ export type ColorPickerProps = {
|
|||||||
class?: any
|
class?: any
|
||||||
ui?: Partial<typeof colorPicker.slots>
|
ui?: Partial<typeof colorPicker.slots>
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
<!-- eslint-disable vue/block-tag-newline -->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { ListboxRootProps, ListboxRootEmits } from 'reka-ui'
|
import type { ListboxRootProps, ListboxRootEmits } from 'reka-ui'
|
||||||
import type { FuseResult } from 'fuse.js'
|
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-label': SlotProps<T>
|
||||||
'item-trailing': SlotProps<T>
|
'item-trailing': SlotProps<T>
|
||||||
} & DynamicSlots<G, SlotProps<T>> & DynamicSlots<T, SlotProps<T>>
|
} & DynamicSlots<G, SlotProps<T>> & DynamicSlots<T, SlotProps<T>>
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script setup lang="ts" generic="G extends CommandPaletteGroup<T>, T extends CommandPaletteItem">
|
<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">
|
<script lang="ts">
|
||||||
import type { VariantProps } from 'tailwind-variants'
|
import type { VariantProps } from 'tailwind-variants'
|
||||||
import type { ContextMenuRootProps, ContextMenuRootEmits, ContextMenuContentProps } from 'reka-ui'
|
import type { ContextMenuRootProps, ContextMenuRootEmits, ContextMenuContentProps } from 'reka-ui'
|
||||||
@@ -93,6 +94,7 @@ export type ContextMenuSlots<T extends { slot?: string }> = {
|
|||||||
'item-label': SlotProps<T>
|
'item-label': SlotProps<T>
|
||||||
'item-trailing': SlotProps<T>
|
'item-trailing': SlotProps<T>
|
||||||
} & DynamicSlots<T, SlotProps<T>>
|
} & DynamicSlots<T, SlotProps<T>>
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script setup lang="ts" generic="T extends ContextMenuItem">
|
<script setup lang="ts" generic="T extends ContextMenuItem">
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
<!-- eslint-disable vue/block-tag-newline -->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { VariantProps } from 'tailwind-variants'
|
import type { VariantProps } from 'tailwind-variants'
|
||||||
import type { DropdownMenuRootProps, DropdownMenuRootEmits, DropdownMenuContentProps, DropdownMenuArrowProps } from 'reka-ui'
|
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-label': SlotProps<T>
|
||||||
'item-trailing': SlotProps<T>
|
'item-trailing': SlotProps<T>
|
||||||
} & DynamicSlots<T, SlotProps<T>>
|
} & DynamicSlots<T, SlotProps<T>>
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script setup lang="ts" generic="T extends DropdownMenuItem">
|
<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> {
|
export interface FormProps<T extends object> {
|
||||||
id?: string | number
|
id?: string | number
|
||||||
|
/** Schema to validate the form state. Supports Standard Schema objects, Yup, Joi, and Superstructs. */
|
||||||
schema?: FormSchema<T>
|
schema?: FormSchema<T>
|
||||||
|
/** An object representing the current state of the form. */
|
||||||
state: Partial<T>
|
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[]
|
validate?: (state: Partial<T>) => Promise<FormError[]> | FormError[]
|
||||||
|
/**
|
||||||
|
* The list of input events that trigger the form validation.
|
||||||
|
* @defaultValue `['blur', 'change', 'input']`
|
||||||
|
*/
|
||||||
validateOn?: FormInputEvents[]
|
validateOn?: FormInputEvents[]
|
||||||
|
/** Disable all inputs inside the form. */
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
|
/**
|
||||||
|
* Delay in milliseconds before validating the form on input events.
|
||||||
|
* @defaultValue `300`
|
||||||
|
*/
|
||||||
validateOnInputDelay?: number
|
validateOnInputDelay?: number
|
||||||
class?: any
|
/**
|
||||||
|
* If true, schema transformations will be applied to the state on submit.
|
||||||
|
* @defaultValue `true`
|
||||||
|
*/
|
||||||
transform?: boolean
|
transform?: boolean
|
||||||
|
class?: any
|
||||||
onSubmit?: ((event: FormSubmitEvent<T>) => void | Promise<void>) | (() => void | Promise<void>)
|
onSubmit?: ((event: FormSubmitEvent<T>) => void | Promise<void>) | (() => void | Promise<void>)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,7 +49,7 @@ export interface FormEmits<T extends object> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface FormSlots {
|
export interface FormSlots {
|
||||||
default(props?: {}): any
|
default(props?: { errors: FormError[] }): any
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -69,7 +89,7 @@ onMounted(async () => {
|
|||||||
nestedForms.value.set(event.formId, { validate: event.validate })
|
nestedForms.value.set(event.formId, { validate: event.validate })
|
||||||
} else if (event.type === 'detach') {
|
} else if (event.type === 'detach') {
|
||||||
nestedForms.value.delete(event.formId)
|
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') {
|
if (event.type !== 'input') {
|
||||||
await _validate({ name: event.name, silent: true, nested: false })
|
await _validate({ name: event.name, silent: true, nested: false })
|
||||||
} else if (event.eager || blurredFields.has(event.name)) {
|
} else if (event.eager || blurredFields.has(event.name)) {
|
||||||
@@ -121,7 +141,7 @@ const blurredFields = new Set<keyof T>()
|
|||||||
function resolveErrorIds(errs: FormError[]): FormErrorWithId[] {
|
function resolveErrorIds(errs: FormError[]): FormErrorWithId[] {
|
||||||
return errs.map(err => ({
|
return errs.map(err => ({
|
||||||
...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) {
|
if (names) {
|
||||||
const otherErrors = errors.value.filter(error => !names.some((name) => {
|
const otherErrors = errors.value.filter(error => !names.some((name) => {
|
||||||
const pattern = inputs.value?.[name]?.pattern
|
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 pathErrors = (await getErrors()).filter(error => names.some((name) => {
|
||||||
const pattern = inputs.value?.[name]?.pattern
|
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)
|
errors.value = otherErrors.concat(pathErrors)
|
||||||
@@ -269,6 +289,6 @@ defineExpose<Form<T>>({
|
|||||||
:class="form({ class: props.class })"
|
:class="form({ class: props.class })"
|
||||||
@submit.prevent="onSubmitWrapper"
|
@submit.prevent="onSubmitWrapper"
|
||||||
>
|
>
|
||||||
<slot />
|
<slot :errors="errors" />
|
||||||
</component>
|
</component>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -31,7 +31,12 @@ export interface FormFieldProps {
|
|||||||
*/
|
*/
|
||||||
size?: FormFieldVariants['size']
|
size?: FormFieldVariants['size']
|
||||||
required?: boolean
|
required?: boolean
|
||||||
|
/** If true, validation on input will be active immediately instead of waiting for a blur event. */
|
||||||
eagerValidation?: boolean
|
eagerValidation?: boolean
|
||||||
|
/**
|
||||||
|
* Delay in milliseconds before validating the form on input events.
|
||||||
|
* @defaultValue `300`
|
||||||
|
*/
|
||||||
validateOnInputDelay?: number
|
validateOnInputDelay?: number
|
||||||
class?: any
|
class?: any
|
||||||
ui?: Partial<typeof formField.slots>
|
ui?: Partial<typeof formField.slots>
|
||||||
@@ -63,7 +68,7 @@ const ui = computed(() => formField({
|
|||||||
|
|
||||||
const formErrors = inject<Ref<FormError[]> | null>('form-errors', null)
|
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())
|
const id = ref(useId())
|
||||||
// Copies id's initial value to bind aria-attributes such as aria-describedby.
|
// 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 emits = defineEmits<InputEmits>()
|
||||||
const slots = defineSlots<InputSlots>()
|
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 { emitFormBlur, emitFormInput, emitFormChange, size: formGroupSize, color, id, name, highlight, disabled, emitFormFocus, ariaAttrs } = useFormField<InputProps>(props, { deferInputValidation: true })
|
||||||
const { orientation, size: buttonGroupSize } = useButtonGroup<InputProps>(props)
|
const { orientation, size: buttonGroupSize } = useButtonGroup<InputProps>(props)
|
||||||
@@ -111,15 +111,19 @@ function autoFocus() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Custom function to handle the v-model properties
|
// Custom function to handle the v-model properties
|
||||||
function updateInput(value: string) {
|
function updateInput(value: string | null) {
|
||||||
if (modelModifiers.trim) {
|
if (modelModifiers.trim) {
|
||||||
value = value.trim()
|
value = value?.trim() ?? null
|
||||||
}
|
}
|
||||||
|
|
||||||
if (modelModifiers.number || props.type === 'number') {
|
if (modelModifiers.number || props.type === 'number') {
|
||||||
value = looseToNumber(value)
|
value = looseToNumber(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (modelModifiers.nullify) {
|
||||||
|
value ||= null
|
||||||
|
}
|
||||||
|
|
||||||
modelValue.value = value
|
modelValue.value = value
|
||||||
emitFormInput()
|
emitFormInput()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -157,7 +157,7 @@ export interface InputMenuSlots<T, M extends boolean> {
|
|||||||
import { computed, ref, toRef, onMounted, toRaw } from 'vue'
|
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 { 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 { defu } from 'defu'
|
||||||
import { isEqual } from 'ohash'
|
import { isEqual } from 'ohash/utils'
|
||||||
import { reactivePick, createReusableTemplate } from '@vueuse/core'
|
import { reactivePick, createReusableTemplate } from '@vueuse/core'
|
||||||
import { useAppConfig } from '#imports'
|
import { useAppConfig } from '#imports'
|
||||||
import { useButtonGroup } from '../composables/useButtonGroup'
|
import { useButtonGroup } from '../composables/useButtonGroup'
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ export interface LinkSlots {
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { isEqual, diff } from 'ohash'
|
import { isEqual, diff } from 'ohash/utils'
|
||||||
import { useForwardProps } from 'reka-ui'
|
import { useForwardProps } from 'reka-ui'
|
||||||
import { reactiveOmit } from '@vueuse/core'
|
import { reactiveOmit } from '@vueuse/core'
|
||||||
import { useRoute } from '#imports'
|
import { useRoute } from '#imports'
|
||||||
@@ -124,11 +124,15 @@ const ui = computed(() => tv({
|
|||||||
function isPartiallyEqual(item1: any, item2: any) {
|
function isPartiallyEqual(item1: any, item2: any) {
|
||||||
const diffedKeys = diff(item1, item2).reduce((filtered, q) => {
|
const diffedKeys = diff(item1, item2).reduce((filtered, q) => {
|
||||||
if (q.type === 'added') {
|
if (q.type === 'added') {
|
||||||
filtered.push(q.key)
|
filtered.add(q.key)
|
||||||
}
|
}
|
||||||
return filtered
|
return filtered
|
||||||
}, [] as string[])
|
}, new Set<string>())
|
||||||
return isEqual(item1, item2, { excludeKeys: key => diffedKeys.includes(key) })
|
|
||||||
|
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) {
|
function isLinkActive({ route: linkRoute, isActive, isExactActive }: any) {
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import type { LinkProps } from '../types'
|
||||||
|
|
||||||
export interface LinkBaseProps {
|
export interface LinkBaseProps {
|
||||||
as?: string
|
as?: string
|
||||||
type?: string
|
type?: string
|
||||||
@@ -6,8 +8,8 @@ export interface LinkBaseProps {
|
|||||||
onClick?: ((e: MouseEvent) => void | Promise<void>) | Array<((e: MouseEvent) => void | Promise<void>)>
|
onClick?: ((e: MouseEvent) => void | Promise<void>) | Array<((e: MouseEvent) => void | Promise<void>)>
|
||||||
href?: string
|
href?: string
|
||||||
navigate?: (e: MouseEvent) => void
|
navigate?: (e: MouseEvent) => void
|
||||||
rel?: string
|
target?: LinkProps['target']
|
||||||
target?: string
|
rel?: LinkProps['rel']
|
||||||
isExternal?: boolean
|
isExternal?: boolean
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
<!-- eslint-disable vue/block-tag-newline -->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { VariantProps } from 'tailwind-variants'
|
import type { VariantProps } from 'tailwind-variants'
|
||||||
import type { NavigationMenuRootProps, NavigationMenuRootEmits, NavigationMenuContentProps, CollapsibleRootProps } from 'reka-ui'
|
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-trailing': SlotProps<T>
|
||||||
'item-content': SlotProps<T>
|
'item-content': SlotProps<T>
|
||||||
} & DynamicSlots<T, SlotProps<T>>
|
} & DynamicSlots<T, SlotProps<T>>
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script setup lang="ts" generic="T extends MaybeArrayOfArrayItem<I>, I extends MaybeArrayOfArray<NavigationMenuItem>">
|
<script setup lang="ts" generic="T extends MaybeArrayOfArrayItem<I>, I extends MaybeArrayOfArray<NavigationMenuItem>">
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { PaginationRootProps, PaginationRootEmits } from 'reka-ui'
|
import type { PaginationRootProps, PaginationRootEmits } from 'reka-ui'
|
||||||
import type { AppConfig } from '@nuxt/schema'
|
import type { AppConfig } from '@nuxt/schema'
|
||||||
import type { RouteLocationRaw } from '#vue-router'
|
|
||||||
import _appConfig from '#build/app.config'
|
import _appConfig from '#build/app.config'
|
||||||
import theme from '#build/ui/pagination'
|
import theme from '#build/ui/pagination'
|
||||||
import { tv } from '../utils/tv'
|
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.
|
* A function to render page controls as links.
|
||||||
* @param page The page number to navigate to.
|
* @param page The page number to navigate to.
|
||||||
*/
|
*/
|
||||||
to?: (page: number) => RouteLocationRaw
|
to?: (page: number) => ButtonProps['to']
|
||||||
class?: any
|
class?: any
|
||||||
ui?: Partial<typeof pagination.slots>
|
ui?: Partial<typeof pagination.slots>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
<!-- eslint-disable vue/block-tag-newline -->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { VariantProps } from 'tailwind-variants'
|
import type { VariantProps } from 'tailwind-variants'
|
||||||
import type { PinInputRootEmits, PinInputRootProps } from 'reka-ui'
|
import type { PinInputRootEmits, PinInputRootProps } from 'reka-ui'
|
||||||
@@ -45,6 +46,7 @@ export type PinInputEmits = PinInputRootEmits & {
|
|||||||
change: [payload: Event]
|
change: [payload: Event]
|
||||||
blur: [payload: Event]
|
blur: [payload: Event]
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<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