Compare commits

..

3 Commits

Author SHA1 Message Date
Benjamin Canac
723065afa7 up 2025-03-07 15:19:38 +01:00
Benjamin Canac
e67305e412 up 2025-03-07 15:11:17 +01:00
Benjamin Canac
33ed3935a3 fix(vue): stub vue-router 2025-03-07 15:00:10 +01:00
179 changed files with 5724 additions and 5937 deletions

View File

@@ -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://ui.nuxt.com/) 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://ui3.nuxt.dev/) 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 placeholder: v3.0.0-alpha.x
validations: validations:
required: true required: true
- type: textarea - type: textarea

View File

@@ -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://ui2.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://ui.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:

View File

@@ -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://ui.nuxt.com/) 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://ui3.nuxt.dev/) 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:

View File

@@ -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://ui2.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://ui.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:

View File

@@ -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://ui.nuxt.com/) 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://ui3.nuxt.dev/) 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:

View File

@@ -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://ui2.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://ui.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:

View File

@@ -63,145 +63,3 @@ jobs:
- name: Publish - name: Publish
run: pnpx pkg-pr-new publish --compact --no-template --pnpm run: pnpx pkg-pr-new publish --compact --no-template --pnpm
starter-nuxt:
needs: build
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: nuxtlabs/nuxt-ui-starter
- name: Store commit SHA
run: |
echo "COMMIT_SHA=$(echo ${{ github.workflow_sha }} | cut -c1-7)" >> $GITHUB_ENV
- name: Install pnpm
uses: pnpm/action-setup@v4
- name: Install node
uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- name: Install latest nuxt/ui
run: pnpm install https://pkg.pr.new/@nuxt/ui@${{ env.COMMIT_SHA }} --lockfile-only
- name: Install dependencies
run: pnpm install
- name: Typecheck
run: pnpm run typecheck
- name: Build
run: pnpm run build
starter-vue:
needs: build
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: nuxtlabs/nuxt-ui-vue-starter
- name: Store commit SHA
run: |
echo "COMMIT_SHA=$(echo ${{ github.workflow_sha }} | cut -c1-7)" >> $GITHUB_ENV
- name: Install pnpm
uses: pnpm/action-setup@v4
- name: Install node
uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- name: Install latest nuxt/ui
run: pnpm install https://pkg.pr.new/@nuxt/ui@${{ env.COMMIT_SHA }} --lockfile-only
- name: Install dependencies
run: pnpm install
- name: Typecheck
run: pnpm run typecheck
- name: Build
run: pnpm run build
# nuxt-ui-pro:
# needs: build
# runs-on: ${{ matrix.os }}
# permissions:
# contents: read
# pull-requests: read
# strategy:
# matrix:
# os: [ubuntu-latest] # macos-latest, windows-latest
# node: [22]
# env:
# NUXT_UI_PRO_LICENSE: ${{ secrets.NUXT_UI_PRO_LICENSE }}
# steps:
# - name: Checkout
# uses: actions/checkout@v4
# with:
# repository: nuxt/ui-pro
# token: ${{ secrets.NUXT_GITHUB_TOKEN }}
# - name: Store commit SHA
# run: |
# echo "COMMIT_SHA=$(echo ${{ github.workflow_sha }} | cut -c1-7)" >> $GITHUB_ENV
# - name: Install pnpm
# uses: pnpm/action-setup@v4
# - name: Install node
# uses: actions/setup-node@v4
# with:
# node-version: 22
# cache: pnpm
# - name: Install latest nuxt/ui
# run: pnpm install https://pkg.pr.new/@nuxt/ui@${{ env.COMMIT_SHA }} --lockfile-only
# - name: Install dependencies
# run: pnpm install
# - name: Prepare
# run: pnpm run dev:prepare
# - name: Typecheck
# run: pnpm run typecheck
# - name: Build
# run: pnpm run build

View File

@@ -1,69 +1,5 @@
# Changelog # Changelog
## [3.0.1](https://github.com/nuxt/ui/compare/v3.0.0...v3.0.1) (2025-03-21)
### ⚠ BREAKING CHANGES
* **Form:** drop explicit support for `zod` and `valibot` (#3617)
### Features
* **components:** handle events in `content` prop ([5dec0e1](https://github.com/nuxt/ui/commit/5dec0e16e28549b8833aaab17a87fada63d6598c))
* **locale:** add Catalan language ([#3550](https://github.com/nuxt/ui/issues/3550)) ([53cf1b4](https://github.com/nuxt/ui/commit/53cf1b4c14a2a0e076e1e77688852e6bd0a28a74))
* **locale:** add Central Kurdish language ([#3566](https://github.com/nuxt/ui/issues/3566)) ([b2034cc](https://github.com/nuxt/ui/commit/b2034ccc91eec6a2842c6f83d159e5aa6fd5f2fd))
* **locale:** add Romanian language ([#3587](https://github.com/nuxt/ui/issues/3587)) ([0229b0f](https://github.com/nuxt/ui/commit/0229b0fd4644a97db7eb3154c3c87a26634dcfbb))
* **locale:** add Urdu language ([#3611](https://github.com/nuxt/ui/issues/3611)) ([126ba23](https://github.com/nuxt/ui/commit/126ba2326f8153e155e1013db92c6ee417117610))
* **locale:** add Uzbek language ([#3548](https://github.com/nuxt/ui/issues/3548)) ([302e04b](https://github.com/nuxt/ui/commit/302e04bd77ae8b165046b264c8d23626e92f8fb5))
### Bug Fixes
* **Calendar:** grey out days outside of displayed month ([#3639](https://github.com/nuxt/ui/issues/3639)) ([a516866](https://github.com/nuxt/ui/commit/a5168666b7dff08e714d57f497737e7a670f457c))
* **ContextMenu/DropdownMenu:** remove `any` from `proxySlots` ([#3623](https://github.com/nuxt/ui/issues/3623)) ([764c41a](https://github.com/nuxt/ui/commit/764c41a0c60dd1c12d39a86af9f5f11b9e6cdc8c))
* **Modal/Slideover/Toast:** prevent unnecessary close instantiation ([f4c417d](https://github.com/nuxt/ui/commit/f4c417d9ef5409b52084bdf9d8cbccee3139709f))
* **module:** handle tailwindcss import without `theme(static)` ([#3630](https://github.com/nuxt/ui/issues/3630)) ([ecff9ab](https://github.com/nuxt/ui/commit/ecff9abc720bdda3a279d5bcfb7b477ff885f2e4))
* **module:** mark functions used in exports as pure ([#3604](https://github.com/nuxt/ui/issues/3604)) ([57efc78](https://github.com/nuxt/ui/commit/57efc78a3b3fa4a54bcd13df47d570a18fba2bc4))
* **RadioGroup:** handle `disabled` on items ([fe0bd83](https://github.com/nuxt/ui/commit/fe0bd83d11b0dfa53b58d423bc917f8e21d73444)), closes [nuxt/ui-pro#911](https://github.com/nuxt/ui-pro/issues/911)
* **Table:** allow links to be opened when [@select](https://github.com/select) is used ([#3580](https://github.com/nuxt/ui/issues/3580)) ([e80cc15](https://github.com/nuxt/ui/commit/e80cc1592fb244dd7692486a4c1ca5b1c2008112))
* **types:** add missing export for Icon ([#3568](https://github.com/nuxt/ui/issues/3568)) ([5e62493](https://github.com/nuxt/ui/commit/5e624933216db95cbfd1b8034b2eb0f13846ae55))
* **unplugin:** include `@compodium/examples` in auto-imports paths ([#3585](https://github.com/nuxt/ui/issues/3585)) ([cc504b8](https://github.com/nuxt/ui/commit/cc504b8a4b69dd76b49659d5c206ef23dcb9e475))
* **useLocale:** unique symbol to use in `@nuxt/ui-pro` ([#3603](https://github.com/nuxt/ui/issues/3603)) ([dec2730](https://github.com/nuxt/ui/commit/dec2730aaea1327434837cfa022ea04056757cbf))
* **vue:** missing unhead context ([#3589](https://github.com/nuxt/ui/issues/3589)) ([0897e9e](https://github.com/nuxt/ui/commit/0897e9ef05fbee4f021f317bb7c2d0b7007f1b75))
### Code Refactoring
* **Form:** drop explicit support for `zod` and `valibot` ([#3617](https://github.com/nuxt/ui/issues/3617)) ([9a4bb34](https://github.com/nuxt/ui/commit/9a4bb34d7d14add0a3199103f4b583e8307d1d6d))
## [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

View File

@@ -11,31 +11,31 @@
[![License][license-src]][license-href] [![License][license-src]][license-href]
[![Nuxt][nuxt-src]][nuxt-href] [![Nuxt][nuxt-src]][nuxt-href]
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. 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.
> [!NOTE] > [!NOTE]
> You are on the `v3` development branch, check out the [v2 branch](https://github.com/nuxt/ui/tree/v2) for Nuxt UI v2. > You are on the `v3` development branch, check out the [dev branch](https://github.com/nuxt/ui/tree/dev) for Nuxt UI v2.
## Documentation ## Documentation
Visit https://ui.nuxt.com to explore the documentation. Visit https://ui3.nuxt.dev to explore the documentation.
## Installation ## Installation
```bash [pnpm] ```bash [pnpm]
pnpm add @nuxt/ui pnpm add @nuxt/ui@next
``` ```
```bash [yarn] ```bash [yarn]
yarn add @nuxt/ui yarn add @nuxt/ui@next
``` ```
```bash [npm] ```bash [npm]
npm install @nuxt/ui npm install @nuxt/ui@next
``` ```
```bash [bun] ```bash [bun]
bun add @nuxt/ui bun add @nuxt/ui@next
``` ```
### Nuxt ### Nuxt
@@ -51,11 +51,11 @@ export default defineNuxtConfig({
2. Import Tailwind CSS and Nuxt UI in your CSS: 2. Import Tailwind CSS and Nuxt UI in your CSS:
```css [assets/css/main.css] ```css [assets/css/main.css]
@import "tailwindcss"; @import "tailwindcss" theme(static);
@import "@nuxt/ui"; @import "@nuxt/ui";
``` ```
Learn more in the [installation guide](https://ui.nuxt.com/getting-started/installation/nuxt). Learn more in the [installation guide](https://ui3.nuxt.dev/getting-started/installation/nuxt).
### Vue ### Vue
@@ -98,11 +98,11 @@ app.mount('#app')
3. Import Tailwind CSS and Nuxt UI in your CSS: 3. Import Tailwind CSS and Nuxt UI in your CSS:
```css [assets/main.css] ```css [assets/main.css]
@import "tailwindcss"; @import "tailwindcss" theme(static);
@import "@nuxt/ui"; @import "@nuxt/ui";
``` ```
Learn more in the [installation guide](https://ui.nuxt.com/getting-started/installation/vue). Learn more in the [installation guide](https://ui3.nuxt.dev/getting-started/installation/vue).
## Credits ## Credits
@@ -119,7 +119,7 @@ Learn more in the [installation guide](https://ui.nuxt.com/getting-started/insta
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/latest.svg?style=flat&colorA=18181B&colorB=28CF8D [npm-version-src]: https://img.shields.io/npm/v/@nuxt/ui/next.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

View File

@@ -6,7 +6,7 @@
}, },
"dependencies": { "dependencies": {
"citty": "^0.1.6", "citty": "^0.1.6",
"consola": "^3.4.2", "consola": "^3.4.0",
"pathe": "^2.0.3", "pathe": "^2.0.3",
"scule": "^1.3.0" "scule": "^1.3.0"
} }

View File

@@ -1,6 +1,7 @@
<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()
@@ -11,6 +12,16 @@ 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; }`)
@@ -22,8 +33,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 },
@@ -50,7 +61,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>
@@ -64,6 +75,7 @@ provide('navigation', mappedNavigation)
<ClientOnly> <ClientOnly>
<LazyUContentSearch <LazyUContentSearch
v-model:search-term="searchTerm"
:files="files" :files="files"
:groups="[{ :groups="[{
id: 'framework', id: 'framework',
@@ -83,5 +95,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 !justify-end !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 !min-h-96 h-136 */
</style> </style>

View File

@@ -1,50 +0,0 @@
<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>

View File

@@ -1,18 +1,7 @@
<template> <template>
<UBanner <UBanner icon="i-lucide-construction" :actions="[{ label: 'Go to Nuxt UI v2', to: 'https://ui.nuxt.com', trailingIcon: 'i-lucide-arrow-right' }]" :close="false">
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>
<span class="font-semibold">Nuxt UI v3</span> is officially released. You're looking at the documentation for <span class="font-semibold">Nuxt UI v3</span>!
</template> </template>
</UBanner> </UBanner>
</template> </template>

View File

@@ -1,51 +0,0 @@
<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>

View File

@@ -41,7 +41,7 @@ const mobileLinks = computed(() => props.links.map(link => ({ ...link, defaultOp
<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.7.1' : 'v2.21.1', to: module === 'ui-pro' ? 'https://ui2.nuxt.com/pro' : 'https://ui2.nuxt.com' }]" :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' }]"
: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"
> >
@@ -82,7 +82,7 @@ const mobileLinks = computed(() => props.links.map(link => ({ ...link, defaultOp
</template> </template>
<template #body> <template #body>
<UNavigationMenu orientation="vertical" :items="mobileLinks" class="-mx-2.5" /> <UNavigationMenu orientation="vertical" :items="mobileLinks" class="-mx-2.5" default-open />
<USeparator type="dashed" class="mt-4 mb-6" /> <USeparator type="dashed" class="mt-4 mb-6" />

View File

@@ -1,6 +1,4 @@
<script setup lang="ts"> <script setup lang="ts">
import { kebabCase } from 'scule'
interface Star { interface Star {
x: number x: number
y: number y: number
@@ -24,8 +22,6 @@ const props = withDefaults(defineProps<{
speed: 'normal' speed: 'normal'
}) })
const route = useRoute()
// Generate random stars // Generate random stars
const generateStars = (count: number): Star[] => { const generateStars = (count: number): Star[] => {
return Array.from({ length: count }, () => { return Array.from({ length: count }, () => {
@@ -39,7 +35,7 @@ const generateStars = (count: number): Star[] => {
} }
// Generate all stars // Generate all stars
const stars = useState<Star[]>(`${kebabCase(route.path)}-sky`, () => generateStars(props.starCount)) const stars = ref<Star[]>(generateStars(props.starCount))
// Compute twinkle animation duration based on speed // Compute twinkle animation duration based on speed
const twinkleDuration = computed(() => { const twinkleDuration = computed(() => {
@@ -54,20 +50,22 @@ const twinkleDuration = computed(() => {
<template> <template>
<div class="absolute pointer-events-none z-[-1] inset-y-0 left-4 right-4 lg:right-[50%] overflow-hidden"> <div class="absolute pointer-events-none z-[-1] inset-y-0 left-4 right-4 lg:right-[50%] overflow-hidden">
<div <ClientOnly>
v-for="star in stars" <div
:key="star.id" v-for="star in stars"
class="star absolute" :key="star.id"
:style="{ class="star absolute"
'left': `${star.x}%`, :style="{
'top': `${star.y}%`, 'left': `${star.x}%`,
'transform': 'translate(-50%, -50%)', 'top': `${star.y}%`,
'--star-size': `${star.size}px`, 'transform': 'translate(-50%, -50%)',
'--star-color': color, '--star-size': `${star.size}px`,
'--twinkle-delay': `${star.twinkleDelay}s`, '--star-color': color,
'--twinkle-duration': twinkleDuration '--twinkle-delay': `${star.twinkleDelay}s`,
}" '--twinkle-duration': twinkleDuration
/> }"
/>
</ClientOnly>
</div> </div>
</template> </template>

View File

@@ -1,12 +1,4 @@
<script setup lang="ts"> <script setup lang="ts">
import { kebabCase } from 'scule'
interface Star {
x: number
y: number
size: number
}
const props = withDefaults(defineProps<{ const props = withDefaults(defineProps<{
starCount?: number starCount?: number
color?: string color?: string
@@ -22,10 +14,8 @@ const props = withDefaults(defineProps<{
}) })
}) })
const route = useRoute()
// Generate random star positions and sizes // Generate random star positions and sizes
const generateStars = (count: number): Star[] => { const generateStars = (count: number) => {
return Array.from({ length: count }, () => ({ return Array.from({ length: count }, () => ({
x: Math.floor(Math.random() * 2000), x: Math.floor(Math.random() * 2000),
y: Math.floor(Math.random() * 2000), y: Math.floor(Math.random() * 2000),
@@ -35,58 +25,52 @@ const generateStars = (count: number): Star[] => {
})) }))
} }
// Define speed configurations once
const speedMap = {
slow: { duration: 200, opacity: 0.5, ratio: 0.3 },
normal: { duration: 150, opacity: 0.75, ratio: 0.3 },
fast: { duration: 100, opacity: 1, ratio: 0.4 }
}
// Use a more efficient approach to generate and store stars
const stars = useState<{ slow: Star[], normal: Star[], fast: Star[] }>(`${kebabCase(route.path)}-stars`, () => {
return {
slow: generateStars(Math.floor(props.starCount * speedMap.slow.ratio)),
normal: generateStars(Math.floor(props.starCount * speedMap.normal.ratio)),
fast: generateStars(Math.floor(props.starCount * speedMap.fast.ratio))
}
})
// Compute star layers with different speeds and opacities // Compute star layers with different speeds and opacities
const starLayers = computed(() => [ const starLayers = computed(() => {
{ stars: stars.value.fast, ...speedMap.fast }, const speedMap = {
{ stars: stars.value.normal, ...speedMap.normal }, slow: { duration: 200, opacity: 0.5 },
{ stars: stars.value.slow, ...speedMap.slow } normal: { duration: 150, opacity: 0.75 },
]) fast: { duration: 100, opacity: 1 }
}
return [
{ stars: generateStars(props.starCount), ...speedMap.fast },
{ stars: generateStars(Math.floor(props.starCount * 0.6)), ...speedMap.normal },
{ stars: generateStars(Math.floor(props.starCount * 0.3)), ...speedMap.slow }
]
})
</script> </script>
<template> <template>
<div class="absolute pointer-events-none z-[-1] inset-y-0 inset-x-5 sm:inset-x-7 lg:inset-x-9 overflow-hidden"> <div class="absolute pointer-events-none z-[-1] inset-y-0 inset-x-5 sm:inset-x-7 lg:inset-x-9 overflow-hidden">
<div class="stars size-full absolute inset-x-0 top-0"> <ClientOnly>
<div <div class="stars size-full absolute inset-x-0 top-0">
v-for="(layer, index) in starLayers"
:key="index"
class="star-layer"
:style="{
'--star-duration': `${layer.duration}s`,
'--star-opacity': layer.opacity,
'--star-color': color
}"
>
<div <div
v-for="(star, starIndex) in layer.stars" v-for="(layer, index) in starLayers"
:key="starIndex" :key="index"
class="star absolute rounded-full" class="star-layer"
:style="{ :style="{
left: `${star.x}px`, '--star-duration': `${layer.duration}s`,
top: `${star.y}px`, '--star-opacity': layer.opacity,
width: `${star.size}px`, '--star-color': color
height: `${star.size}px`,
backgroundColor: 'var(--star-color)',
opacity: 'var(--star-opacity)'
}" }"
/> >
<div
v-for="(star, starIndex) in layer.stars"
:key="starIndex"
class="star absolute rounded-full"
:style="{
left: `${star.x}px`,
top: `${star.y}px`,
width: `${star.size}px`,
height: `${star.size}px`,
backgroundColor: 'var(--star-color)',
opacity: 'var(--star-opacity)'
}"
/>
</div>
</div> </div>
</div> </ClientOnly>
</div> </div>
</template> </template>

View File

@@ -11,9 +11,7 @@ function getEmojiFlag(locale: string): string {
const languageToCountry: Record<string, string> = { const languageToCountry: Record<string, string> = {
ar: 'sa', // Arabic -> Saudi Arabia ar: 'sa', // Arabic -> Saudi Arabia
bn: 'bd', // Bengali -> Bangladesh bn: 'bd', // Bengali -> Bangladesh
ca: 'es', // Catalan -> Spain
cs: 'cz', // Czech -> Czech Republic (note: modern country code is actually 'cz') cs: 'cz', // Czech -> Czech Republic (note: modern country code is actually 'cz')
ckb: 'iq', // Central Kurdish -> Iraq
da: 'dk', // Danish -> Denmark da: 'dk', // Danish -> Denmark
el: 'gr', // Greek -> Greece el: 'gr', // Greek -> Greece
et: 'ee', // Estonian -> Estonia et: 'ee', // Estonian -> Estonia
@@ -26,7 +24,6 @@ function getEmojiFlag(locale: string): string {
nb: 'no', // Norwegian Bokmål -> Norway nb: 'no', // Norwegian Bokmål -> Norway
sv: 'se', // Swedish -> Sweden sv: 'se', // Swedish -> Sweden
uk: 'ua', // Ukrainian -> Ukraine uk: 'ua', // Ukrainian -> Ukraine
ur: 'pk', // Urdu -> Pakistan
vi: 'vn' // Vietnamese -> Vietnam vi: 'vn' // Vietnamese -> Vietnam
} }

View File

@@ -22,7 +22,7 @@ async function onSubmit(event: FormSubmitEvent<Schema>) {
</script> </script>
<template> <template>
<UForm :schema="schema" :state="state" class="space-y-4" @submit="onSubmit"> <UForm :schema="v.safeParser(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>

View File

@@ -3,7 +3,7 @@ withDefaults(defineProps<{
title: string title: string
description: string description: string
component: string component: string
module?: string module: string
}>(), { }>(), {
module: '' module: ''
}) })

View File

@@ -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: ''

View File

@@ -81,6 +81,7 @@ function setBlackAsPrimary(value: boolean) {
<div class="grid grid-cols-3 gap-1 -mx-2"> <div class="grid grid-cols-3 gap-1 -mx-2">
<ThemePickerButton <ThemePickerButton
chip="primary"
label="Black" label="Black"
:selected="appConfig.theme.blackAsPrimary" :selected="appConfig.theme.blackAsPrimary"
@click="setBlackAsPrimary(true)" @click="setBlackAsPrimary(true)"
@@ -89,7 +90,6 @@ function setBlackAsPrimary(value: boolean) {
<span class="inline-block w-2 h-2 rounded-full bg-black dark:bg-white" /> <span class="inline-block w-2 h-2 rounded-full bg-black dark:bg-white" />
</template> </template>
</ThemePickerButton> </ThemePickerButton>
<ThemePickerButton <ThemePickerButton
v-for="color in primaryColors" v-for="color in primaryColors"
:key="color" :key="color"

View File

@@ -5,10 +5,6 @@ defineProps<{
chip?: string chip?: string
selected?: boolean selected?: boolean
}>() }>()
const slots = defineSlots<{
leading: () => any
}>()
</script> </script>
<template> <template>
@@ -21,7 +17,7 @@ const slots = defineSlots<{
class="capitalize ring-(--ui-border) rounded-[calc(var(--ui-radius))] text-[11px]" class="capitalize ring-(--ui-border) rounded-[calc(var(--ui-radius))] text-[11px]"
:class="[selected ? 'bg-(--ui-bg-elevated)' : 'hover:bg-(--ui-bg-elevated)/50']" :class="[selected ? 'bg-(--ui-bg-elevated)' : 'hover:bg-(--ui-bg-elevated)/50']"
> >
<template v-if="chip || !!slots.leading" #leading> <template v-if="chip" #leading>
<slot name="leading"> <slot name="leading">
<span <span
class="inline-block size-2 rounded-full" class="inline-block size-2 rounded-full"

View File

@@ -1,5 +1,6 @@
<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<{
@@ -14,6 +15,16 @@ 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; }`)
@@ -37,7 +48,7 @@ useHead({
}) })
useSeoMeta({ useSeoMeta({
titleTemplate: '%s - Nuxt UI', titleTemplate: '%s - Nuxt UI v3',
title: String(props.error.statusCode) title: String(props.error.statusCode)
}) })
@@ -56,16 +67,17 @@ 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',

View File

@@ -97,7 +97,7 @@ design_system:
``` ```
```css [main.css] ```css [main.css]
@import "tailwindcss"; @import "tailwindcss" theme(static);
@import "@nuxt/ui"; @import "@nuxt/ui";
:root { :root {

View File

@@ -82,8 +82,6 @@ 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
@@ -109,6 +107,23 @@ 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>
@@ -157,9 +172,14 @@ 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" />
<AdsCarbon /> <AdsPro />
<AdsCarbon /> -->
</template> </template>
</UContentToc> </UContentToc>
</template> </template>

View File

@@ -119,8 +119,7 @@ onMounted(() => {
/> />
</template> </template>
<LazyStarsBg /> <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" />
</UPageHero> </UPageHero>

View File

@@ -178,7 +178,6 @@ 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
@@ -200,7 +199,6 @@ 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,14 +275,16 @@ 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 [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): 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):
1. Install the [TemPad Dev Chrome Extension](https://chromewebstore.google.com/detail/tempad-dev/lgoeakbaikpkihoiphamaeopmliaimpc) 1. Install the [TeamPad 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 TemPad Dev panel) 2. Open your Figma file with Nuxt UI components (reload the page if you don't see the TeamPad Dev panel)
3. Install the `@nuxt` (or `@nuxt/pro` for Nuxt UI Pro) in TemPad Dev's plugins section 3. Install the `@nuxt` in TeamPad 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
![TemPad Dev Nuxt UI Plugin](/pro/figma/teampad-dev-nuxt-ui-plugin.gif){.w-full .rounded .mb-2 .max-w-[636px]} ![TeamPad Dev Nuxt UI Plugin](/pro/figma/teampad-dev-nuxt-ui-plugin.gif){.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.*

View File

@@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
// @ts-expect-error yaml is not typed // @ts-expect-error yaml is not typed
import page from '.figma.yml' import page from '.figma.yml'
import { animate } from 'motion-v' import { animate } from 'motion'
import { joinURL } from 'ufo' import { joinURL } from 'ufo'
const { url } = useSiteConfig() const { url } = useSiteConfig()
@@ -233,7 +233,6 @@ 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"

View File

@@ -76,7 +76,7 @@ useIntersectionObserver(contributorsRef, ([entry]) => {
:key="feature.title" :key="feature.title"
as-child as-child
:initial="{ opacity: 0, transform: 'translateX(-10px)' }" :initial="{ opacity: 0, transform: 'translateX(-10px)' }"
:while-in-view="{ opacity: 1, transform: 'translateX(0)' }" :in-view="{ opacity: 1, transform: 'translateX(0)' }"
:transition="{ delay: 0.2 + 0.4 * index }" :transition="{ delay: 0.2 + 0.4 * index }"
:in-view-options="{ once: true }" :in-view-options="{ once: true }"
> >
@@ -85,7 +85,7 @@ useIntersectionObserver(contributorsRef, ([entry]) => {
</div> </div>
</template> </template>
<LazySkyBg /> <SkyBg />
<div class="h-[344px] lg:h-full lg:relative w-full lg:min-h-[calc(100vh-var(--ui-header-height)-1px)] overflow-hidden"> <div class="h-[344px] lg:h-full lg:relative w-full lg:min-h-[calc(100vh-var(--ui-header-height)-1px)] overflow-hidden">
<UPageMarquee <UPageMarquee
@@ -93,7 +93,7 @@ useIntersectionObserver(contributorsRef, ([entry]) => {
:overlay="false" :overlay="false"
:ui="{ :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', 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:flex-col lg:animate-[marquee-vertical_var(--duration)_linear_infinite] lg:rtl:animate-[marquee-vertical-rtl_var(--duration)_linear_infinite] lg:h-[fit-content]' 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 <ULink
@@ -118,7 +118,7 @@ useIntersectionObserver(contributorsRef, ([entry]) => {
:overlay="false" :overlay="false"
:ui="{ :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', 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: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]' 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 <ULink
@@ -147,7 +147,7 @@ useIntersectionObserver(contributorsRef, ([entry]) => {
:key="feature.title" :key="feature.title"
as="li" as="li"
:initial="{ opacity: 0, transform: 'translateY(10px)' }" :initial="{ opacity: 0, transform: 'translateY(10px)' }"
:while-in-view="{ opacity: 1, transform: 'translateY(0)' }" :in-view="{ opacity: 1, transform: 'translateY(0)' }"
:transition="{ delay: 0.1 * index }" :transition="{ delay: 0.1 * index }"
:in-view-options="{ once: true }" :in-view-options="{ once: true }"
class="flex items-start gap-x-3 relative group" class="flex items-start gap-x-3 relative group"
@@ -189,7 +189,7 @@ useIntersectionObserver(contributorsRef, ([entry]) => {
:links="page.design_system.links" :links="page.design_system.links"
orientation="horizontal" orientation="horizontal"
> >
<MDC :value="page.design_system.code" cache-key="index-design-system-code" /> <MDC :value="page.design_system.code" />
</UPageSection> </UPageSection>
<USeparator /> <USeparator />
@@ -201,10 +201,10 @@ useIntersectionObserver(contributorsRef, ([entry]) => {
orientation="horizontal" orientation="horizontal"
> >
<template #description> <template #description>
<MDC :value="page.component_customization.description" cache-key="index-component-customization-description" /> <MDC :value="page.component_customization.description" />
</template> </template>
<MDC :value="page.component_customization.code" cache-key="index-component-customization-code" /> <MDC :value="page.component_customization.code" />
</UPageSection> </UPageSection>
<USeparator /> <USeparator />
@@ -261,7 +261,7 @@ useIntersectionObserver(contributorsRef, ([entry]) => {
</UButton> </UButton>
</template> </template>
<LazyStarsBg /> <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="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"> <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">

View File

@@ -17,10 +17,7 @@ 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 price: $149 - $349
# 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
@@ -39,7 +36,6 @@ pricing:
- 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:
@@ -54,7 +50,6 @@ 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:
@@ -70,7 +65,6 @@ 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:
@@ -180,7 +174,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: 'Software engineer' description: 'Web developer and UnJS member'
to: 'https://x.com/soubiran_' to: 'https://x.com/soubiran_'
target: _blank target: _blank
avatar: avatar:

View File

@@ -71,8 +71,7 @@ onMounted(() => {
<template> <template>
<UMain> <UMain>
<UPageHero headline="License Activation" :title="title" :description="description" :ui="{ container: 'relative overflow-hidden', wrapper: 'lg:px-12', description: 'text-pretty' }"> <UPageHero headline="License Activation" :title="title" :description="description" :ui="{ container: 'relative overflow-hidden', wrapper: 'lg:px-12', description: 'text-pretty' }">
<LazyStarsBg /> <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="px-4 py-10 lg:border border-(--ui-border) bg-(--ui-bg)"> <div class="px-4 py-10 lg:border border-(--ui-border) bg-(--ui-bg)">

View File

@@ -32,7 +32,7 @@ useSeoMeta({
<MDC :value="page.hero.description" tag="span" unwrap="p" cache-key="pro-hero-description" /> <MDC :value="page.hero.description" tag="span" unwrap="p" cache-key="pro-hero-description" />
</template> </template>
<LazyStarsBg /> <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" />
@@ -82,11 +82,11 @@ useSeoMeta({
}" }"
> >
<template #description> <template #description>
<Motion :initial="{ opacity: 0, transform: 'translateY(10px)' }" :while-in-view="{ opacity: 1, transform: 'translateY(0)' }" :in-view-options="{ once: true }" :transition="{ delay: 0.2 }"> <Motion :initial="{ opacity: 0, transform: 'translateY(10px)' }" :in-view="{ opacity: 1, transform: 'translateY(0)' }" :in-view-options="{ once: true }" :transition="{ delay: 0.2 }">
<MDC :value="page.testimonial.quote" tag="span" unwrap="p" class="before:content-[open-quote] after:content-[close-quote]" cache-key="pro-testimonial-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)' }" :while-in-view="{ opacity: 1, transform: 'translateY(0)' }" :in-view-options="{ once: true }" :transition="{ delay: 0.3 }"> <Motion :initial="{ opacity: 0, transform: 'translateY(10px)' }" :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"
@@ -103,7 +103,7 @@ useSeoMeta({
}" }"
class="border-t border-(--ui-border)" class="border-t border-(--ui-border)"
> >
<Motion as-child :initial="{ height: 0 }" :while-in-view="{ height: 'auto' }" :transition="{ delay: 0.4, duration: 1 }"> <Motion as-child :initial="{ height: 0 }" :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>
@@ -196,7 +196,7 @@ useSeoMeta({
class="overflow-hidden" class="overflow-hidden"
orientation="horizontal" orientation="horizontal"
> >
<LazyStarsBg /> <StarsBg />
<video <video
class="rounded-[var(--ui-radius)] z-10" class="rounded-[var(--ui-radius)] z-10"

View File

@@ -27,10 +27,8 @@ useSeoMeta({
<MDC :value="page.pricing.title" unwrap="p" cache-key="pro-pricing-title" /> <MDC :value="page.pricing.title" unwrap="p" cache-key="pro-pricing-title" />
</template> </template>
<LazyStarsBg /> <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
v-bind="page.pricing.freePlan" v-bind="page.pricing.freePlan"
@@ -44,7 +42,6 @@ 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'"
@@ -56,8 +53,6 @@ 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>

View File

@@ -18,8 +18,7 @@ useSeoMeta({
<template> <template>
<div class="relative"> <div class="relative">
<UPageHero :links="page.links" :ui="{ container: 'relative' }"> <UPageHero :links="page.links" :ui="{ container: 'relative' }">
<LazyStarsBg /> <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" />
<template #title> <template #title>
@@ -51,7 +50,7 @@ useSeoMeta({
</template> </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)' }" :while-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)' }" :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"

View File

@@ -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', titleTemplate: '%s - Nuxt UI v3',
title, title,
ogTitle: 'Nuxt UI Roadmap', ogTitle: 'Nuxt UI Roadmap',
description description

View File

@@ -41,16 +41,16 @@ export default defineNuxtPlugin({
const primaryColor = localStorage.getItem('nuxt-ui-primary'); const primaryColor = localStorage.getItem('nuxt-ui-primary');
if (primaryColor !== 'black') { if (primaryColor !== 'black') {
html = html.replace( html = html.replace(
/(--ui-color-primary-\\d{2,3}:\\s*var\\(--color-)${appConfig.ui.colors.primary}(-\\d{2,3}.*?\\))/g, /(--ui-color-primary-\\d{2,3}:\\s*var\\()--color-${appConfig.ui.colors.primary}-(\\d{2,3}\\))/g,
\`$1\${primaryColor}$2\` \`$1--color-\${primaryColor}-$2\`
); );
} }
} }
if (localStorage.getItem('nuxt-ui-neutral')) { if (localStorage.getItem('nuxt-ui-neutral')) {
let neutralColor = localStorage.getItem('nuxt-ui-neutral'); let neutralColor = localStorage.getItem('nuxt-ui-neutral');
html = html.replace( html = html.replace(
/(--ui-color-neutral-\\d{2,3}:\\s*var\\(--color-)${appConfig.ui.colors.neutral}(-\\d{2,3}.*?\\))/g, /(--ui-color-neutral-\\d{2,3}:\\s*var\\()--color-${appConfig.ui.colors.neutral}-(\\d{2,3}\\))/g,
\`$1\${neutralColor === 'neutral' ? 'old-neutral' : neutralColor}$2\` \`$1--color-\${neutralColor === 'neutral' ? 'old-neutral' : neutralColor}-$2\`
); );
} }

View File

@@ -1,9 +1,14 @@
--- ---
title: Introduction navigation.title: Introduction
description: 'Nuxt UI harnesses the combined strengths of Reka UI, Tailwind CSS, and Tailwind Variants to offer developers an unparalleled set of tools for creating sophisticated, accessible, and highly performant user interfaces.' title: Nuxt UI v3
description: 'A comprehensive, Nuxt-integrated UI library providing a rich set of fully-styled, accessible and highly customizable components for building modern web applications.'
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
@@ -19,7 +24,7 @@ This transition empowers Nuxt UI to become a more comprehensive and flexible UI
### Tailwind CSS v4 ### Tailwind CSS v4
Nuxt UI integrates the latest Tailwind CSS v4, bringing significant improvements: Nuxt UI v3 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.
@@ -42,7 +47,7 @@ This integration unifies the styling of components, ensuring consistency and cod
### TypeScript Integration ### TypeScript Integration
Nuxt UI offers significantly improved TypeScript integration, providing a superior developer experience: Nuxt UI v3 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
@@ -107,7 +112,11 @@ Key points to consider:
## FAQ ## FAQ
::accordion ::accordion
::accordion-item{label="Is Nuxt UI compatible with standalone Vue projects?"} ::accordion-item{label="What are the main considerations when upgrading to Nuxt UI v3?"}
The transition to v3 involves significant changes, including new component structures, updated theming approaches, and revised TypeScript definitions. We recommend a careful, incremental upgrade process, starting with thorough testing in a development environment.
::
::accordion-item{label="Is Nuxt UI v3 compatible with standalone Vue projects?"}
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.
:: ::
@@ -115,19 +124,23 @@ 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 work with other CSS frameworks like UnoCSS?"} ::accordion-item{label="Will Nuxt UI v3 work with other CSS frameworks like UnoCSS?"}
Nuxt UI is currently designed to work exclusively with Tailwind CSS. While there's interest in UnoCSS support, implementing it would require significant changes to the theme structure due to differences in class naming conventions. As a result, we don't have plans to add UnoCSS support. Nuxt UI v3 is currently designed to work exclusively with Tailwind CSS. While there's interest in UnoCSS support, implementing it would require significant changes to the theme structure due to differences in class naming conventions. As a result, we don't have plans to add UnoCSS support in v3.
:: ::
::accordion-item{label="How does Nuxt UI handle accessibility?"} ::accordion-item{label="How does Nuxt UI v3 handle accessibility?"}
Nuxt UI enhances accessibility through Reka UI integration. This provides automatic ARIA attributes, keyboard navigation support, intelligent focus management, and screen reader announcements. While offering a strong foundation, proper implementation and testing in your specific use case remains crucial for full accessibility compliance. For more detailed information, refer to [Reka UI's accessibility documentation](https://reka-ui.com/docs/overview/accessibility). Nuxt UI v3 enhances accessibility through Reka UI integration. This provides automatic ARIA attributes, keyboard navigation support, intelligent focus management, and screen reader announcements. While offering a strong foundation, proper implementation and testing in your specific use case remains crucial for full accessibility compliance. For more detailed information, refer to [Reka UI's accessibility documentation](https://reka-ui.com/docs/overview/accessibility).
:: ::
::accordion-item{label="What is the testing approach for Nuxt UI?"} ::accordion-item{label="What is the testing approach for Nuxt UI v3?"}
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. Nuxt UI v3 ensures reliability with 1000+ Vitest tests, covering core functionality and accessibility. This robust testing suite supports the library's stability and serves as a reference for developers.
::
::accordion-item{label="Is this version stable and suitable for production use?"}
Nuxt UI v3 is now in beta and is stable enough to be used in production. We now recommend using v3 over v2. We welcome feedback from users to help improve the library further. Feel free to report any issues you encounter on our [GitHub repository](https://github.com/nuxt/ui/issues).
:: ::
:: ::
: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. 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.

View File

@@ -20,24 +20,24 @@ Looking for the **Vue** version?
::steps{level="4"} ::steps{level="4"}
#### Install the Nuxt UI package #### Install the Nuxt UI v3 beta package
::code-group{sync="pm"} ::code-group{sync="pm"}
```bash [pnpm] ```bash [pnpm]
pnpm add @nuxt/ui pnpm add @nuxt/ui@next
``` ```
```bash [yarn] ```bash [yarn]
yarn add @nuxt/ui yarn add @nuxt/ui@next
``` ```
```bash [npm] ```bash [npm]
npm install @nuxt/ui npm install @nuxt/ui@next
``` ```
```bash [bun] ```bash [bun]
bun add @nuxt/ui bun add @nuxt/ui@next
``` ```
:: ::
@@ -59,7 +59,7 @@ export default defineNuxtConfig({
::code-group ::code-group
```css [app/assets/css/main.css] ```css [app/assets/css/main.css]
@import "tailwindcss"; @import "tailwindcss" theme(static);
@import "@nuxt/ui"; @import "@nuxt/ui";
``` ```
@@ -72,6 +72,10 @@ export default defineNuxtConfig({
:: ::
::warning
The `theme(static)` is required since [`tailwindcss@4.0.8`](https://github.com/tailwindlabs/tailwindcss/releases/tag/v4.0.8) introduced a breaking change to only expose used CSS variables.
::
::callout{icon="i-simple-icons-visualstudiocode"} ::callout{icon="i-simple-icons-visualstudiocode"}
It's recommended to install the [Tailwind CSS IntelliSense](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss) extension for VSCode and add the following settings: It's recommended to install the [Tailwind CSS IntelliSense](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss) extension for VSCode and add the following settings:
@@ -81,11 +85,7 @@ 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 +108,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#ui](https://github.com/nuxt/starter/tree/ui) template with Nuxt UI pre-configured. Start your project using the [nuxt/starter#ui3](https://github.com/nuxt/starter/tree/ui3) template with Nuxt UI v3 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 ui <my-app> npx nuxi init -t ui3 <my-app>
``` ```
::note ::note
@@ -225,19 +225,30 @@ This option adds the `transition-colors` class on components with hover or activ
## Continuous Releases ## Continuous 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. 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.
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. 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.
```diff [package.json] ::code-group{sync="pm"}
{
"dependencies": { ```bash [pnpm]
- "@nuxt/ui": "^3.0.0", pnpm add https://pkg.pr.new/@nuxt/ui@5385f84
+ "@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.
:: ::

View File

@@ -20,24 +20,24 @@ Looking for the **Nuxt** version?
::steps{level="4"} ::steps{level="4"}
#### Install the Nuxt UI package #### Install the Nuxt UI v3 beta package
::code-group{sync="pm"} ::code-group{sync="pm"}
```bash [pnpm] ```bash [pnpm]
pnpm add @nuxt/ui pnpm add @nuxt/ui@next
``` ```
```bash [yarn] ```bash [yarn]
yarn add @nuxt/ui yarn add @nuxt/ui@next
``` ```
```bash [npm] ```bash [npm]
npm install @nuxt/ui npm install @nuxt/ui@next
``` ```
```bash [bun] ```bash [bun]
bun add @nuxt/ui bun add @nuxt/ui@next
``` ```
:: ::
@@ -102,10 +102,14 @@ app.mount('#app')
#### Import Tailwind CSS and Nuxt UI in your CSS #### Import Tailwind CSS and Nuxt UI in your CSS
```css [assets/main.css] ```css [assets/main.css]
@import "tailwindcss"; @import "tailwindcss" theme(static);
@import "@nuxt/ui"; @import "@nuxt/ui";
``` ```
::warning
The `theme(static)` is required since [`tailwindcss@4.0.8`](https://github.com/tailwindlabs/tailwindcss/releases/tag/v4.0.8) introduced a breaking change to only expose used CSS variables.
::
::tip ::tip
Import the CSS file in your `main.ts`. Import the CSS file in your `main.ts`.
@@ -141,11 +145,7 @@ 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 +168,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 pre-configured. 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.
Create a new project locally by running the following command: Create a new project locally by running the following command:
@@ -313,19 +313,30 @@ This option adds the `transition-colors` class on components with hover or activ
## Continuous Releases ## Continuous 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. 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.
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. 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.
```diff [package.json] ::code-group{sync="pm"}
{
"dependencies": { ```bash [pnpm]
- "@nuxt/ui": "^3.0.0", pnpm add https://pkg.pr.new/@nuxt/ui@5385f84
+ "@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.
:: ::

View File

@@ -28,7 +28,7 @@ For a detailed walkthrough of all changes, refer to the official **Tailwind CSS
::code-group ::code-group
```css [app/assets/css/main.css] ```css [app/assets/css/main.css]
@import "tailwindcss"; @import "tailwindcss" theme(static);
``` ```
```ts [nuxt.config.ts] ```ts [nuxt.config.ts]
@@ -56,19 +56,19 @@ npx @tailwindcss/upgrade
::::code-group{sync="pm"} ::::code-group{sync="pm"}
```bash [pnpm] ```bash [pnpm]
pnpm add @nuxt/ui pnpm add @nuxt/ui@next
``` ```
```bash [yarn] ```bash [yarn]
yarn add @nuxt/ui yarn add @nuxt/ui@next
``` ```
```bash [npm] ```bash [npm]
npm install @nuxt/ui npm install @nuxt/ui@next
``` ```
```bash [bun] ```bash [bun]
bun add @nuxt/ui bun add @nuxt/ui@next
``` ```
:::: ::::
@@ -81,19 +81,19 @@ bun add @nuxt/ui
::::code-group{sync="pm"} ::::code-group{sync="pm"}
```bash [pnpm] ```bash [pnpm]
pnpm add @nuxt/ui-pro pnpm add @nuxt/ui-pro@next
``` ```
```bash [yarn] ```bash [yarn]
yarn add @nuxt/ui-pro yarn add @nuxt/ui-pro@next
``` ```
```bash [npm] ```bash [npm]
npm install @nuxt/ui-pro npm install @nuxt/ui-pro@next
``` ```
```bash [bun] ```bash [bun]
bun add @nuxt/ui-pro bun add @nuxt/ui-pro@next
``` ```
:::: ::::
@@ -109,7 +109,7 @@ bun add @nuxt/ui-pro
:::div :::div
```css [app/assets/css/main.css]{2} ```css [app/assets/css/main.css]{2}
@import "tailwindcss"; @import "tailwindcss" theme(static);
@import "@nuxt/ui"; @import "@nuxt/ui";
``` ```
@@ -119,7 +119,7 @@ bun add @nuxt/ui-pro
:::div :::div
```css [app/assets/css/main.css]{2} ```css [app/assets/css/main.css]{2}
@import "tailwindcss"; @import "tailwindcss" theme(static);
@import "@nuxt/ui-pro"; @import "@nuxt/ui-pro";
``` ```
@@ -130,7 +130,7 @@ bun add @nuxt/ui-pro
#ui #ui
:::div :::div
5. Wrap your app with the [App](/components/app) component: 5. Wrap you app with the [App](/components/app) component:
::: :::
#ui-pro #ui-pro
@@ -145,7 +145,7 @@ export default defineNuxtConfig({
}) })
``` ```
6. Wrap your app with the [App](/components/app) component: 6. Wrap you app with the [App](/components/app) component:
::: :::
:: ::
@@ -439,10 +439,7 @@ This change affects the following components: `Modal`, `Popover`, `Slideover`, `
This change affects the following components: `Modal`, `Slideover`. This change affects the following components: `Modal`, `Slideover`.
:: ::
6. The `Toast` component `timeout` prop has been renamed to `duration`:
### Changed composables
1. The `useToast()` composable `timeout` prop has been renamed to `duration`:
```diff ```diff
<script setup lang="ts"> <script setup lang="ts">
@@ -453,79 +450,6 @@ const toast = useToast()
</script> </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

View File

@@ -6,7 +6,7 @@ navigation.icon: i-lucide-swatch-book
## Tailwind CSS ## Tailwind CSS
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. 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.
### `@theme` ### `@theme`
@@ -17,7 +17,7 @@ Tailwind CSS v4 takes a CSS-first configuration approach, you now customize your
:::div :::div
```css [app/assets/css/main.css] ```css [app/assets/css/main.css]
@import "tailwindcss"; @import "tailwindcss" theme(static);
@import "@nuxt/ui"; @import "@nuxt/ui";
@theme static { @theme static {
@@ -45,7 +45,7 @@ Tailwind CSS v4 takes a CSS-first configuration approach, you now customize your
:::div :::div
```css [app/assets/css/main.css] ```css [app/assets/css/main.css]
@import "tailwindcss"; @import "tailwindcss" theme(static);
@import "@nuxt/ui-pro"; @import "@nuxt/ui-pro";
@theme static { @theme static {
@@ -87,7 +87,7 @@ This can be useful when writing Tailwind CSS classes in markdown files with [`@n
:::div :::div
```css [app/assets/css/main.css] ```css [app/assets/css/main.css]
@import "tailwindcss"; @import "tailwindcss" theme(static);
@import "@nuxt/ui"; @import "@nuxt/ui";
@source "../../../content"; @source "../../../content";
@@ -101,7 +101,7 @@ This can be useful when writing Tailwind CSS classes in markdown files with [`@n
:::div :::div
```css [app/assets/css/main.css] ```css [app/assets/css/main.css]
@import "tailwindcss"; @import "tailwindcss" theme(static);
@import "@nuxt/ui-pro"; @import "@nuxt/ui-pro";
@source "../../../content"; @source "../../../content";
@@ -380,7 +380,7 @@ You can change which shade is used for each color on light and dark mode:
:::div{class="*:!mb-0 *:!mt-2.5"} :::div{class="*:!mb-0 *:!mt-2.5"}
```css [app/assets/css/main.css] ```css [app/assets/css/main.css]
@import "tailwindcss"; @import "tailwindcss" theme(static);
@import "@nuxt/ui"; @import "@nuxt/ui";
:root { :root {
@@ -398,7 +398,7 @@ You can change which shade is used for each color on light and dark mode:
:::div{class="*:!mb-0 *:!mt-2.5"} :::div{class="*:!mb-0 *:!mt-2.5"}
```css [app/assets/css/main.css] ```css [app/assets/css/main.css]
@import "tailwindcss"; @import "tailwindcss" theme(static);
@import "@nuxt/ui-pro"; @import "@nuxt/ui-pro";
:root { :root {
@@ -434,7 +434,7 @@ You cannot set `primary: 'black'`{lang="ts-type"} in your [`vite.config.ts`](#co
:::div{class="*:!mb-0 *:!mt-2.5"} :::div{class="*:!mb-0 *:!mt-2.5"}
```css [app/assets/css/main.css] ```css [app/assets/css/main.css]
@import "tailwindcss"; @import "tailwindcss" theme(static);
@import "@nuxt/ui"; @import "@nuxt/ui";
:root { :root {
@@ -452,7 +452,7 @@ You cannot set `primary: 'black'`{lang="ts-type"} in your [`vite.config.ts`](#co
:::div{class="*:!mb-0 *:!mt-2.5"} :::div{class="*:!mb-0 *:!mt-2.5"}
```css [app/assets/css/main.css] ```css [app/assets/css/main.css]
@import "tailwindcss"; @import "tailwindcss" theme(static);
@import "@nuxt/ui-pro"; @import "@nuxt/ui-pro";
:root { :root {
@@ -564,7 +564,7 @@ You can customize these CSS variables to tailor the appearance of your applicati
:::div{class="*:!mb-0 *:!mt-2.5"} :::div{class="*:!mb-0 *:!mt-2.5"}
```css [app/assets/css/main.css] ```css [app/assets/css/main.css]
@import "tailwindcss"; @import "tailwindcss" theme(static);
@import "@nuxt/ui"; @import "@nuxt/ui";
:root { :root {
@@ -584,7 +584,7 @@ You can customize these CSS variables to tailor the appearance of your applicati
:::div{class="*:!mb-0 *:!mt-2.5"} :::div{class="*:!mb-0 *:!mt-2.5"}
```css [app/assets/css/main.css] ```css [app/assets/css/main.css]
@import "tailwindcss"; @import "tailwindcss" theme(static);
@import "@nuxt/ui-pro"; @import "@nuxt/ui-pro";
:root { :root {
@@ -625,7 +625,7 @@ You can customize the default radius value using the default Tailwind CSS variab
:::div{class="*:!mb-0 *:!mt-2.5"} :::div{class="*:!mb-0 *:!mt-2.5"}
```css [app/assets/css/main.css] ```css [app/assets/css/main.css]
@import "tailwindcss"; @import "tailwindcss" theme(static);
@import "@nuxt/ui"; @import "@nuxt/ui";
:root { :root {
@@ -639,7 +639,7 @@ You can customize the default radius value using the default Tailwind CSS variab
:::div{class="*:!mb-0 *:!mt-2.5"} :::div{class="*:!mb-0 *:!mt-2.5"}
```css [app/assets/css/main.css] ```css [app/assets/css/main.css]
@import "tailwindcss"; @import "tailwindcss" theme(static);
@import "@nuxt/ui-pro"; @import "@nuxt/ui-pro";
:root { :root {
@@ -670,7 +670,7 @@ You can customize the default container width using the default Tailwind CSS var
:::div{class="*:!mb-0 *:!mt-2.5"} :::div{class="*:!mb-0 *:!mt-2.5"}
```css [app/assets/css/main.css] ```css [app/assets/css/main.css]
@import "tailwindcss"; @import "tailwindcss" theme(static);
@import "@nuxt/ui"; @import "@nuxt/ui";
@theme { @theme {
@@ -688,7 +688,7 @@ You can customize the default container width using the default Tailwind CSS var
:::div{class="*:!mb-0 *:!mt-2.5"} :::div{class="*:!mb-0 *:!mt-2.5"}
```css [app/assets/css/main.css] ```css [app/assets/css/main.css]
@import "tailwindcss"; @import "tailwindcss" theme(static);
@import "@nuxt/ui-pro"; @import "@nuxt/ui-pro";
@theme { @theme {

View File

@@ -19,7 +19,7 @@ Nuxt UI automatically registers the [`@nuxt/fonts`](https://github.com/nuxt/font
:::div :::div
```css [app/assets/css/main.css] ```css [app/assets/css/main.css]
@import "tailwindcss"; @import "tailwindcss" theme(static);
@import "@nuxt/ui"; @import "@nuxt/ui";
@theme { @theme {
@@ -33,7 +33,7 @@ Nuxt UI automatically registers the [`@nuxt/fonts`](https://github.com/nuxt/font
:::div :::div
```css [app/assets/css/main.css] ```css [app/assets/css/main.css]
@import "tailwindcss"; @import "tailwindcss" theme(static);
@import "@nuxt/ui-pro"; @import "@nuxt/ui-pro";
@theme { @theme {

View File

@@ -17,11 +17,6 @@ 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]
@@ -36,42 +31,13 @@ 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">
import type { Messages } from '@nuxt/ui' const locale = defineLocale({
const locale = defineLocale<Messages>({
name: 'My custom locale', name: 'My custom locale',
code: 'en', code: 'en',
dir: 'ltr', dir: 'ltr',
@@ -88,35 +54,6 @@ const locale = defineLocale<Messages>({
</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:
@@ -179,11 +116,6 @@ 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'
@@ -198,28 +130,6 @@ 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
@@ -228,11 +138,6 @@ 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'
@@ -257,38 +162,6 @@ 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

View File

@@ -17,11 +17,6 @@ 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]
@@ -36,43 +31,15 @@ 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 type { Messages } from '@nuxt/ui' import { defineLocale } from '@nuxt/ui/composables/defineLocale'
import { defineLocale } from '@nuxt/ui/composables/defineLocale.js'
const locale = defineLocale<Messages>({ const locale = defineLocale({
name: 'My custom locale', name: 'My custom locale',
code: 'en', code: 'en',
dir: 'ltr', dir: 'ltr',
@@ -89,36 +56,6 @@ const locale = defineLocale<Messages>({
</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:
@@ -194,11 +131,6 @@ 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'
@@ -214,29 +146,6 @@ 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
@@ -245,11 +154,6 @@ 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'
@@ -277,41 +181,6 @@ 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

View File

@@ -1,13 +1,13 @@
--- ---
title: Contribution Guide title: Contribution Guide
description: 'A comprehensive guide on contributing to Nuxt UI, including project structure, development workflow, and best practices.' description: 'A comprehensive guide on contributing to Nuxt UI v3, 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://ui.nuxt.com/) 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://ui3.nuxt.dev/) and existing [issues](https://github.com/nuxt/ui/issues?q=is%3Aissue%20is%3Aopen%20sort%3Aupdated-desc%20label%3Av3).
:: ::
## Project Structure ## Project Structure

View File

@@ -166,31 +166,6 @@ slots:
:placeholder{class="h-48 m-4"} :placeholder{class="h-48 m-4"}
:: ::
### Handle Only
Use the `handle-only` prop to only allow the Drawer to be dragged by the handle.
::component-code
---
prettier: true
props:
handleOnly: true
slots:
default: |
<UButton label="Open" color="neutral" variant="subtle" trailing-icon="i-lucide-chevron-up" />
content: |
<Placeholder class="h-48 m-4" />
---
:u-button{label="Open" color="neutral" variant="subtle" trailing-icon="i-lucide-chevron-up"}
#content
:placeholder{class="h-48 m-4"}
::
### Overlay ### Overlay
Use the `overlay` prop to control whether the Drawer has an overlay or not. Defaults to `true`. Use the `overlay` prop to control whether the Drawer has an overlay or not. Defaults to `true`.
@@ -218,14 +193,13 @@ slots:
### Scale background ### Scale background
Use the `should-scale-background` prop to scale the background when the Drawer is open, creating a visual depth effect. You can set the `set-background-color-on-scale` prop to `false` to prevent changing the background color. Use the `should-scale-background` prop to scale the background when the Drawer is open, creating a visual depth effect.
::component-code ::component-code
--- ---
prettier: true prettier: true
props: props:
shouldScaleBackground: true shouldScaleBackground: true
setBackgroundColorOnScale: true
slots: slots:
default: | default: |
@@ -243,12 +217,12 @@ slots:
:: ::
::warning ::warning
Make sure to add the `data-vaul-drawer-wrapper` directive to a parent element of your app to make this work. Make sure to add the `vaul-drawer-wrapper` directive to a parent element of your app to make this work.
```vue [app.vue] ```vue [app.vue]
<template> <template>
<UApp> <UApp>
<div class="bg-(--ui-bg)" data-vaul-drawer-wrapper> <div class="bg-(--ui-bg)" vaul-drawer-wrapper>
<NuxtLayout> <NuxtLayout>
<NuxtPage /> <NuxtPage />
</NuxtLayout> </NuxtLayout>
@@ -261,7 +235,7 @@ Make sure to add the `data-vaul-drawer-wrapper` directive to a parent element of
export default defineNuxtConfig({ export default defineNuxtConfig({
app: { app: {
rootAttrs: { rootAttrs: {
'data-vaul-drawer-wrapper': '', 'vaul-drawer-wrapper': '',
'class': 'bg-(--ui-bg)' 'class': 'bg-(--ui-bg)'
} }
} }

View File

@@ -9,7 +9,7 @@ links:
## Usage ## Usage
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. 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.
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` - 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). - `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).
::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**.

View File

@@ -8,6 +8,7 @@ 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

View File

@@ -34,7 +34,7 @@ export default defineNuxtConfig({
}, },
$production: { $production: {
site: { site: {
url: 'https://ui.nuxt.com' url: 'https://ui3.nuxt.dev'
} }
}, },
@@ -55,7 +55,7 @@ export default defineNuxtConfig({
}] }]
}, },
rootAttrs: { rootAttrs: {
'data-vaul-drawer-wrapper': '', 'vaul-drawer-wrapper': '',
'class': 'bg-(--ui-bg)' 'class': 'bg-(--ui-bg)'
} }
}, },
@@ -90,56 +90,7 @@ export default defineNuxtConfig({
'/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 },
'/composables': { redirect: '/composables/define-shortcuts', prerender: false }, '/composables': { redirect: '/composables/define-shortcuts', prerender: false }
// v2 redirects
'/getting-started/theming': { redirect: { to: '/getting-started/theme', statusCode: 301 }, prerender: false },
'/pro/getting-started/**': { redirect: { to: '/getting-started/installation/pro/nuxt', statusCode: 301 }, prerender: false },
'/playground': { redirect: { to: '/getting-started/installation/nuxt', statusCode: 301 }, prerender: false },
'/pro/guide/**': { redirect: { to: '/getting-started/installation/pro/nuxt', statusCode: 301 }, prerender: false },
'/pro/prose/**': { redirect: { to: '/getting-started/typography#vue-components', statusCode: 301 }, prerender: false },
'/components/range': { redirect: { to: '/components/slider', statusCode: 301 }, prerender: false },
'/components/date-picker': { redirect: { to: '/components/calendar#as-a-datepicker', statusCode: 301 }, prerender: false },
'/components/dropdown': { redirect: { to: '/components/dropdown-menu', statusCode: 301 }, prerender: false },
'/components/notification': { redirect: { to: '/components/toast', statusCode: 301 }, prerender: false },
'/components/vertical-navigation': { redirect: { to: '/components/navigation-menu', statusCode: 301 }, prerender: false },
'/components/horizontal-navigation': { redirect: { to: '/components/navigation-menu', statusCode: 301 }, prerender: false },
'/components/divider': { redirect: { to: '/components/separator', statusCode: 301 }, prerender: false },
'/components/toggle': { redirect: { to: '/components/switch', statusCode: 301 }, prerender: false },
'/components/form-group': { redirect: { to: '/components/form-field', statusCode: 301 }, prerender: false },
'/pro/components': { redirect: { to: '/components', statusCode: 301 }, prerender: false },
'/pro/components/docs/docs-search': { redirect: { to: '/components/content-search', statusCode: 301 }, prerender: false },
'/pro/components/docs-search': { redirect: { to: '/components/content-search', statusCode: 301 }, prerender: false },
'/pro/components/landing-hero': { redirect: { to: '/components/page-hero', statusCode: 301 }, prerender: false },
'/pro/components/landing-cta': { redirect: { to: '/components/page-cta', statusCode: 301 }, prerender: false },
'/pro/components/landing-card': { redirect: { to: '/components/page-card', statusCode: 301 }, prerender: false },
'/pro/components/landing-section': { redirect: { to: '/components/page-section', statusCode: 301 }, prerender: false },
'/pro/components/landing-faq': { redirect: { to: '/components/page-accordion', statusCode: 301 }, prerender: false },
'/pro/components/landing-grid': { redirect: { to: '/components/page-grid', statusCode: 301 }, prerender: false },
'/pro/components/landing-logos': { redirect: { to: '/components/page-logos', statusCode: 301 }, prerender: false },
'/pro/components/landing-testimonial': { redirect: { to: '/components/page-card#as-a-testimonial', statusCode: 301 }, prerender: false },
'/pro/components/blog-list': { redirect: { to: '/components/blog-posts', statusCode: 301 }, prerender: false },
'/pro/components/color-mode-toggle': { redirect: { to: '/components/color-mode-switch', statusCode: 301 }, prerender: false },
'/pro/components/dashboard-card': { redirect: { to: '/components/page-card', statusCode: 301 }, prerender: false },
'/pro/components/dashboard-layout': { redirect: { to: '/components/dashboard-group', statusCode: 301 }, prerender: false },
'/pro/components/dashboard-modal': { redirect: { to: '/components/modal', statusCode: 301 }, prerender: false },
'/pro/components/dashboard-navbar-toggle': { redirect: { to: '/components/dashboard-sidebar-toggle', statusCode: 301 }, prerender: false },
'/pro/components/dashboard-page': { redirect: { to: '/components/dashboard-panel', statusCode: 301 }, prerender: false },
'/pro/components/dashboard-panel-content': { redirect: { to: '/components/dashboard-panel', statusCode: 301 }, prerender: false },
'/pro/components/dashboard-panel-handle': { redirect: { to: '/components/dashboard-resize-handle', statusCode: 301 }, prerender: false },
'/pro/components/dashboard-section': { redirect: { to: '/components/page-card', statusCode: 301 }, prerender: false },
'/pro/components/dashboard-sidebar-links': { redirect: { to: '/components/navigation-menu', statusCode: 301 }, prerender: false },
'/pro/components/dashboard-slideover': { redirect: { to: '/components/slideover', statusCode: 301 }, prerender: false },
'/pro/components/navigation-accordion': { redirect: { to: '/components/content-navigation', statusCode: 301 }, prerender: false },
'/pro/components/navigation-links': { redirect: { to: '/components/content-navigation', statusCode: 301 }, prerender: false },
'/pro/components/navigation-tree': { redirect: { to: '/components/content-navigation', statusCode: 301 }, prerender: false },
'/pro/components/page-error': { redirect: { to: '/components/error', statusCode: 301 }, prerender: false },
'/pro/components/footer-links': { redirect: { to: '/components/navigation-menu', statusCode: 301 }, prerender: false },
'/pro/components/header-links': { redirect: { to: '/components/navigation-menu', statusCode: 301 }, prerender: false },
'/pro/components/pricing-card': { redirect: { to: '/components/pricing-plan', statusCode: 301 }, prerender: false },
'/pro/components/pricing-grid': { redirect: { to: '/components/pricing-plans', statusCode: 301 }, prerender: false },
'/pro/components/pricing-switch': { redirect: { to: '/components/switch', statusCode: 301 }, prerender: false },
'/pro/components/**': { redirect: { to: '/components/**', statusCode: 301 }, prerender: false },
'/releases': { redirect: 'https://github.com/nuxt/ui/releases', prerender: false }
}, },
future: { future: {
@@ -153,10 +104,9 @@ 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
@@ -177,12 +127,7 @@ 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: {
@@ -224,12 +169,12 @@ export default defineNuxtConfig({
}, },
llms: { llms: {
domain: 'https://ui.nuxt.com', domain: 'https://ui3.nuxt.dev',
title: 'Nuxt UI', title: 'Nuxt UI v3',
description: 'A comprehensive, Nuxt-integrated UI library providing a rich set of fully-styled, accessible and highly customizable components for building modern web applications.', 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 Full Documentation', title: 'Nuxt UI v3 Full Documentation',
description: 'This is the full documentation for Nuxt UI. It includes all the Markdown files written with the MDC syntax.' description: 'This is the full documentation for Nuxt UI v3. It includes all the Markdown files written with the MDC syntax.'
}, },
sections: [ sections: [
{ {

View File

@@ -4,33 +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.31", "@iconify-json/lucide": "^1.2.28",
"@iconify-json/simple-icons": "^1.2.29", "@iconify-json/simple-icons": "^1.2.27",
"@iconify-json/vscode-icons": "^1.2.16", "@iconify-json/vscode-icons": "^1.2.16",
"@nuxt/content": "^3.4.0", "@nuxt/content": "^3.3.0",
"@nuxt/image": "^1.10.0", "@nuxt/image": "^1.9.0",
"@nuxt/ui": "latest", "@nuxt/ui": "latest",
"@nuxt/ui-pro": "https://pkg.pr.new/@nuxt/ui-pro@d96a086", "@nuxt/ui-pro": "https://pkg.pr.new/@nuxt/ui-pro@02b7ea0",
"@nuxthub/core": "^0.8.18", "@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": "^13.0.0", "@vueuse/nuxt": "^12.8.2",
"joi": "^17.13.3", "joi": "^17.13.3",
"motion-v": "0.13.1", "motion": "^12.4.10",
"nuxt": "^3.16.1", "motion-v": "0.11.1",
"nuxt": "^3.15.4",
"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": "^5.0.5", "nuxt-og-image": "^4.2.0",
"prettier": "^3.5.3", "prettier": "^3.5.3",
"shiki-transformer-color-highlight": "^1.0.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": "^1.0.0", "valibot": "^0.42.1",
"yup": "^1.6.1", "yup": "^1.6.1",
"zod": "^3.24.2" "zod": "^3.24.2"
}, },
"devDependencies": { "devDependencies": {
"wrangler": "^3.114.2" "wrangler": "^3.114.0"
} }
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

View File

@@ -188,7 +188,6 @@ const countries: Country[] = [
{ name: 'United Arab Emirates', code: 'AE', emoji: '🇦🇪' }, { name: 'United Arab Emirates', code: 'AE', emoji: '🇦🇪' },
{ name: 'United Kingdom', code: 'GB', emoji: '🇬🇧' }, { name: 'United Kingdom', code: 'GB', emoji: '🇬🇧' },
{ name: 'United States', code: 'US', emoji: '🇺🇸' }, { name: 'United States', code: 'US', emoji: '🇺🇸' },
{ name: 'Pakistan', code: 'PK', emoji: '🇵🇰' },
{ name: 'Uruguay', code: 'UY', emoji: '🇺🇾' }, { name: 'Uruguay', code: 'UY', emoji: '🇺🇾' },
{ name: 'Uzbekistan', code: 'UZ', emoji: '🇺🇿' }, { name: 'Uzbekistan', code: 'UZ', emoji: '🇺🇿' },
{ name: 'Vanuatu', code: 'VU', emoji: '🇻🇺' }, { name: 'Vanuatu', code: 'VU', emoji: '🇻🇺' },

View File

@@ -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.1", "version": "3.0.0-beta.2",
"packageManager": "pnpm@10.6.5", "packageManager": "pnpm@10.6.1",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://github.com/nuxt/ui.git" "url": "git+https://github.com/nuxt/ui.git"
}, },
"homepage": "https://ui.nuxt.com", "homepage": "https://ui3.nuxt.dev",
"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": "NODE_OPTIONS='--max-old-space-size=8192' nuxi build docs", "docs:build": "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,25 +75,25 @@
"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" "release": "release-it --preRelease=beta --npm.tag=next"
}, },
"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.11.0", "@nuxt/fonts": "^0.10.3",
"@nuxt/icon": "^1.11.0", "@nuxt/icon": "^1.10.3",
"@nuxt/kit": "^3.16.1", "@nuxt/kit": "^3.15.4",
"@nuxt/schema": "^3.16.1", "@nuxt/schema": "^3.15.4",
"@nuxtjs/color-mode": "^3.5.2", "@nuxtjs/color-mode": "^3.5.2",
"@tailwindcss/postcss": "^4.0.15", "@tailwindcss/postcss": "^4.0.12",
"@tailwindcss/vite": "^4.0.15", "@tailwindcss/vite": "^4.0.12",
"@tanstack/vue-table": "^8.21.2", "@tanstack/vue-table": "^8.21.2",
"@unhead/vue": "^2.0.0-rc.13", "@unhead/vue": "^1.11.20",
"@vueuse/core": "^13.0.0", "@vueuse/core": "^12.8.2",
"@vueuse/integrations": "^13.0.0", "@vueuse/integrations": "^12.8.2",
"colortranslator": "^4.1.0", "colortranslator": "^4.1.0",
"consola": "^3.4.2", "consola": "^3.4.0",
"defu": "^6.1.4", "defu": "^6.1.4",
"embla-carousel-auto-height": "^8.5.2", "embla-carousel-auto-height": "^8.5.2",
"embla-carousel-auto-scroll": "^8.5.2", "embla-carousel-auto-scroll": "^8.5.2",
@@ -108,34 +108,32 @@
"mlly": "^1.7.4", "mlly": "^1.7.4",
"ohash": "^2.0.11", "ohash": "^2.0.11",
"pathe": "^2.0.3", "pathe": "^2.0.3",
"reka-ui": "^2.1.0", "reka-ui": "^2.0.2",
"scule": "^1.3.0", "scule": "^1.3.0",
"tailwind-variants": "^1.0.0", "tailwind-variants": "^0.3.1",
"tailwindcss": "^4.0.15", "tailwindcss": "^4.0.12",
"tinyglobby": "^0.2.12", "tinyglobby": "^0.2.12",
"unplugin": "^2.2.1", "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.4.1", "vaul-vue": "^0.3.0"
"vue": "^3.5.13",
"vue-router": "^4.5.0"
}, },
"devDependencies": { "devDependencies": {
"@nuxt/eslint-config": "^1.2.0", "@nuxt/eslint-config": "^1.1.0",
"@nuxt/module-builder": "^0.8.4", "@nuxt/module-builder": "^0.8.4",
"@nuxt/test-utils": "^3.17.2", "@nuxt/test-utils": "^3.17.1",
"@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.22.0", "eslint": "^9.21.0",
"happy-dom": "^17.4.4", "happy-dom": "^17.1.2",
"joi": "^17.13.3", "joi": "^17.13.3",
"nuxt": "^3.16.1", "nuxt": "^3.15.4",
"release-it": "^18.1.2", "release-it": "^18.1.2",
"superstruct": "^2.0.2", "superstruct": "^2.0.2",
"valibot": "^1.0.0", "valibot": "^0.42.1",
"vitest": "^3.0.9", "vitest": "^3.0.8",
"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",
@@ -148,9 +146,12 @@
"@nuxt/ui": "workspace:*", "@nuxt/ui": "workspace:*",
"chokidar": "3.6.0", "chokidar": "3.6.0",
"debug": "4.3.7", "debug": "4.3.7",
"rollup": "4.34.9", "happy-dom": "17.1.2",
"rollup": "4.32.1",
"typescript": "5.6.3", "typescript": "5.6.3",
"unplugin": "^2.2.1", "unimport": "3.14.5",
"unplugin": "^2.2.0",
"vue": "3.5.13",
"vue-tsc": "2.2.0" "vue-tsc": "2.2.0"
}, },
"pnpm": { "pnpm": {

View File

@@ -2,9 +2,8 @@
<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 Playground</title> <title>Nuxt UI ❤️ Vue</title>
</head> </head>
<body> <body>
<div id="app" class="isolate"></div> <div id="app" class="isolate"></div>

View File

@@ -15,9 +15,9 @@
"vue-router": "^4.5.0" "vue-router": "^4.5.0"
}, },
"devDependencies": { "devDependencies": {
"@vitejs/plugin-vue": "^5.2.3", "@vitejs/plugin-vue": "^5.2.1",
"typescript": "^5.6.3", "typescript": "^5.6.3",
"vite": "^6.2.2", "vite": "^6.2.1",
"vue-tsc": "^2.2.0" "vue-tsc": "^2.2.0"
} }
} }

View File

@@ -1,8 +0,0 @@
<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>

Before

Width:  |  Height:  |  Size: 316 B

View File

@@ -84,7 +84,7 @@ defineShortcuts({
<template> <template>
<UApp :toaster="(appConfig.toaster as any)"> <UApp :toaster="(appConfig.toaster as any)">
<div class="h-screen w-screen overflow-hidden flex min-h-0 bg-(--ui-bg)" data-vaul-drawer-wrapper> <div class="h-screen w-screen overflow-hidden flex min-h-0 bg-(--ui-bg)" vaul-drawer-wrapper>
<UNavigationMenu :items="items" orientation="vertical" class="hidden lg:flex border-e border-(--ui-border) overflow-y-auto w-48 p-4" /> <UNavigationMenu :items="items" orientation="vertical" class="hidden lg:flex border-e border-(--ui-border) overflow-y-auto w-48 p-4" />
<UNavigationMenu :items="items" orientation="horizontal" class="lg:hidden border-b border-(--ui-border) [&>div]:min-w-min overflow-x-auto" /> <UNavigationMenu :items="items" orientation="horizontal" class="lg:hidden border-b border-(--ui-border) [&>div]:min-w-min overflow-x-auto" />

View File

@@ -1,4 +1,4 @@
@import "tailwindcss" source("../../../.."); @import "tailwindcss" theme(static) source("../../../..");
@import "@nuxt/ui"; @import "@nuxt/ui";
@theme static { @theme static {

View File

@@ -85,7 +85,7 @@ defineShortcuts({
<template> <template>
<template v-if="!$route.path.startsWith('/__nuxt_ui__')"> <template v-if="!$route.path.startsWith('/__nuxt_ui__')">
<UApp :toaster="appConfig.toaster"> <UApp :toaster="appConfig.toaster">
<div class="h-screen w-screen overflow-hidden flex flex-col lg:flex-row min-h-0 bg-(--ui-bg)" data-vaul-drawer-wrapper> <div class="h-screen w-screen overflow-hidden flex flex-col lg:flex-row min-h-0 bg-(--ui-bg)" vaul-drawer-wrapper>
<UNavigationMenu :items="items" orientation="vertical" class="hidden lg:flex border-e border-(--ui-border) overflow-y-auto w-48 p-4" /> <UNavigationMenu :items="items" orientation="vertical" class="hidden lg:flex border-e border-(--ui-border) overflow-y-auto w-48 p-4" />
<UNavigationMenu :items="items" orientation="horizontal" class="lg:hidden border-b border-(--ui-border) [&>div]:min-w-min overflow-x-auto" /> <UNavigationMenu :items="items" orientation="horizontal" class="lg:hidden border-b border-(--ui-border) [&>div]:min-w-min overflow-x-auto" />

View File

@@ -1,4 +1,4 @@
@import "tailwindcss" source("../../../.."); @import "tailwindcss" theme(static) source("../../../..");
@import "@nuxt/ui"; @import "@nuxt/ui";
@theme static { @theme static {

View File

@@ -20,7 +20,7 @@ const inset = ref(false)
</template> </template>
</UDrawer> </UDrawer>
<UDrawer should-scale-background title="Drawer with `should-scale-background`" description="You need to add the `data-vaul-drawer-wrapper` directive to your content to make it work." :inset="inset"> <UDrawer should-scale-background title="Drawer with `should-scale-background`" description="You need to add the `vaul-drawer-wrapper` directive to your content to make it work." :inset="inset">
<UButton color="neutral" variant="outline" label="Open with scale" /> <UButton color="neutral" variant="outline" label="Open with scale" />
<template #body> <template #body>
@@ -28,7 +28,7 @@ const inset = ref(false)
</template> </template>
</UDrawer> </UDrawer>
<UDrawer title="Drawer with bottom direction" direction="bottom" :inset="inset"> <UDrawer title="Drawer with bottom direction" direction="bottom" :handle="false" :inset="inset">
<UButton color="neutral" variant="outline" label="Open on bottom" /> <UButton color="neutral" variant="outline" label="Open on bottom" />
<template #body> <template #body>
@@ -36,7 +36,7 @@ const inset = ref(false)
</template> </template>
</UDrawer> </UDrawer>
<UDrawer title="Drawer with left direction" direction="left" :inset="inset"> <UDrawer title="Drawer with left direction" direction="left" :handle="false" :inset="inset">
<UButton color="neutral" variant="outline" label="Open on left" /> <UButton color="neutral" variant="outline" label="Open on left" />
<template #body> <template #body>
@@ -44,7 +44,7 @@ const inset = ref(false)
</template> </template>
</UDrawer> </UDrawer>
<UDrawer title="Drawer with top direction" direction="top" :inset="inset"> <UDrawer title="Drawer with top direction" direction="top" :handle="false" :inset="inset">
<UButton color="neutral" variant="outline" label="Open on top" /> <UButton color="neutral" variant="outline" label="Open on top" />
<template #body> <template #body>
@@ -52,7 +52,7 @@ const inset = ref(false)
</template> </template>
</UDrawer> </UDrawer>
<UDrawer title="Drawer with right direction" direction="right" :inset="inset"> <UDrawer title="Drawer with right direction" direction="right" :handle="false" :inset="inset">
<UButton color="neutral" variant="outline" label="Open on right" /> <UButton color="neutral" variant="outline" label="Open on right" />
<template #body> <template #body>

View File

@@ -8,10 +8,10 @@
"generate": "nuxi generate" "generate": "nuxi generate"
}, },
"dependencies": { "dependencies": {
"@iconify-json/lucide": "^1.2.31", "@iconify-json/lucide": "^1.2.28",
"@iconify-json/simple-icons": "^1.2.29", "@iconify-json/simple-icons": "^1.2.27",
"@nuxt/ui": "latest", "@nuxt/ui": "latest",
"@nuxthub/core": "^0.8.18", "@nuxthub/core": "^0.8.18",
"nuxt": "^3.16.1" "nuxt": "^3.15.4"
} }
} }

4091
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -6,10 +6,13 @@
"enabled": true "enabled": true
}, },
"ignoreDeps": [ "ignoreDeps": [
"happy-dom",
"valibot30",
"valibot31",
"typescript", "typescript",
"vue-tsc" "vue-tsc"
], ],
"baseBranches": ["v2", "v3"], "baseBranches": ["dev", "v3"],
"packageRules": [{ "packageRules": [{
"matchBaseBranches": ["v3"], "matchBaseBranches": ["v3"],
"labels": ["v3"] "labels": ["v3"]

View File

@@ -9,40 +9,40 @@ export interface ModuleOptions {
/** /**
* Prefix for components * Prefix for components
* @defaultValue `U` * @defaultValue `U`
* @link https://ui.nuxt.com/getting-started/installation/nuxt#prefix * @link https://ui3.nuxt.dev/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://ui.nuxt.com/getting-started/installation/nuxt#fonts * @link https://ui3.nuxt.dev/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://ui.nuxt.com/getting-started/installation/nuxt#colormode * @link https://ui3.nuxt.dev/getting-started/installation/nuxt#colormode
*/ */
colorMode?: boolean colorMode?: boolean
/** /**
* Customize how the theme is generated * Customize how the theme is generated
* @link https://ui.nuxt.com/getting-started/theme * @link https://ui3.nuxt.dev/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://ui.nuxt.com/getting-started/installation/nuxt#themecolors * @link https://ui3.nuxt.dev/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://ui.nuxt.com/getting-started/installation/nuxt#themetransitions * @link https://ui3.nuxt.dev/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.16.0' nuxt: '>=3.13.1'
}, },
docs: 'https://ui.nuxt.com/getting-started/installation/nuxt' docs: 'https://ui3.nuxt.dev/getting-started/installation/nuxt'
}, },
defaults: defaultOptions, defaults: defaultOptions,
async setup(options, nuxt) { async setup(options, nuxt) {

View File

@@ -20,11 +20,7 @@ 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: [ exclude: [/[\\/]node_modules[\\/](?!\.pnpm|@nuxt\/ui)/, /[\\/]\.git[\\/]/, /[\\/]\.nuxt[\\/]/],
/[\\/]node_modules[\\/](?!\.pnpm|@nuxt\/ui|@compodium\/examples)/,
/[\\/]\.git[\\/]/,
/[\\/]\.nuxt[\\/]/
],
resolvers: [ resolvers: [
(componentName) => { (componentName) => {
if (overrideNames.has(componentName)) if (overrideNames.has(componentName))

View File

@@ -1,4 +1,3 @@
<!-- 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'
@@ -61,7 +60,6 @@ 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">

View File

@@ -1,11 +1,12 @@
<script lang="ts"> <script lang="ts">
import type { ConfigProviderProps, TooltipProviderProps } from 'reka-ui' import type { ConfigProviderProps, TooltipProviderProps } from 'reka-ui'
import type { ToasterProps, Locale, Messages } from '../types' import { localeContextInjectionKey } from '../composables/useLocale'
import type { ToasterProps, Locale } from '../types'
export interface AppProps<T extends Messages = Messages> extends Omit<ConfigProviderProps, 'useId' | 'dir' | 'locale'> { export interface AppProps extends Omit<ConfigProviderProps, 'useId' | 'dir' | 'locale'> {
tooltip?: TooltipProviderProps tooltip?: TooltipProviderProps
toaster?: ToasterProps | null toaster?: ToasterProps | null
locale?: Locale<T> locale?: Locale
} }
export interface AppSlots { export interface AppSlots {
@@ -17,15 +18,14 @@ export default {
} }
</script> </script>
<script setup lang="ts" generic="T extends Messages = Messages"> <script setup lang="ts">
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<T>>() const props = defineProps<AppProps>()
defineSlots<AppSlots>() defineSlots<AppSlots>()
const configProviderProps = useForwardProps(reactivePick(props, 'scrollBody')) const configProviderProps = useForwardProps(reactivePick(props, 'scrollBody'))

View File

@@ -1,4 +1,3 @@
<!-- 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'
@@ -52,7 +51,6 @@ 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">

View File

@@ -60,7 +60,6 @@ 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 = withDefaults(defineProps<ButtonProps>(), { const props = withDefaults(defineProps<ButtonProps>(), {
active: undefined, active: undefined,

View File

@@ -1,4 +1,3 @@
<!-- 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'
@@ -71,7 +70,6 @@ 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">

View File

@@ -1,4 +1,3 @@
<!-- 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'
@@ -131,7 +130,6 @@ 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">

View File

@@ -1,13 +1,12 @@
<!-- 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, ContextMenuContentEmits } from 'reka-ui' import type { ContextMenuRootProps, ContextMenuRootEmits, ContextMenuContentProps } from 'reka-ui'
import type { AppConfig } from '@nuxt/schema' import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config' import _appConfig from '#build/app.config'
import theme from '#build/ui/context-menu' import theme from '#build/ui/context-menu'
import { tv } from '../utils/tv' import { tv } from '../utils/tv'
import type { AvatarProps, KbdProps, LinkProps } from '../types' import type { AvatarProps, KbdProps, LinkProps } from '../types'
import type { DynamicSlots, PartialString, EmitsToProps } from '../types/utils' import type { DynamicSlots, PartialString } from '../types/utils'
const appConfigContextMenu = _appConfig as AppConfig & { ui: { contextMenu: Partial<typeof theme> } } const appConfigContextMenu = _appConfig as AppConfig & { ui: { contextMenu: Partial<typeof theme> } }
@@ -23,7 +22,7 @@ export interface ContextMenuItem extends Omit<LinkProps, 'type' | 'raw' | 'custo
icon?: string icon?: string
color?: ContextMenuVariants['color'] color?: ContextMenuVariants['color']
avatar?: AvatarProps avatar?: AvatarProps
content?: Omit<ContextMenuContentProps, 'as' | 'asChild' | 'forceMount'> & Partial<EmitsToProps<ContextMenuContentEmits>> content?: Omit<ContextMenuContentProps, 'as' | 'asChild' | 'forceMount'>
kbds?: KbdProps['value'][] | KbdProps[] kbds?: KbdProps['value'][] | KbdProps[]
/** /**
* The item type. * The item type.
@@ -67,7 +66,7 @@ export interface ContextMenuProps<T> extends Omit<ContextMenuRootProps, 'dir'> {
*/ */
externalIcon?: boolean | string externalIcon?: boolean | string
/** The content of the menu. */ /** The content of the menu. */
content?: Omit<ContextMenuContentProps, 'as' | 'asChild' | 'forceMount'> & Partial<EmitsToProps<ContextMenuContentEmits>> content?: Omit<ContextMenuContentProps, 'as' | 'asChild' | 'forceMount'>
/** /**
* Render the menu in a portal. * Render the menu in a portal.
* @defaultValue true * @defaultValue true
@@ -94,7 +93,6 @@ 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">
@@ -114,7 +112,7 @@ const emits = defineEmits<ContextMenuEmits>()
const slots = defineSlots<ContextMenuSlots<T>>() const slots = defineSlots<ContextMenuSlots<T>>()
const rootProps = useForwardPropsEmits(reactivePick(props, 'modal'), emits) const rootProps = useForwardPropsEmits(reactivePick(props, 'modal'), emits)
const contentProps = toRef(() => props.content) const contentProps = toRef(() => props.content as ContextMenuContentProps)
const proxySlots = omit(slots, ['default']) as Record<string, ContextMenuSlots<T>[string]> const proxySlots = omit(slots, ['default']) as Record<string, ContextMenuSlots<T>[string]>
const ui = computed(() => contextMenu({ const ui = computed(() => contextMenu({
@@ -140,7 +138,7 @@ const ui = computed(() => contextMenu({
:loading-icon="loadingIcon" :loading-icon="loadingIcon"
:external-icon="externalIcon" :external-icon="externalIcon"
> >
<template v-for="(_, name) in proxySlots" #[name]="slotData"> <template v-for="(_, name) in proxySlots" #[name]="slotData: any">
<slot :name="name" v-bind="slotData" /> <slot :name="name" v-bind="slotData" />
</template> </template>
</UContextMenuContent> </UContextMenuContent>

View File

@@ -1,17 +1,16 @@
<script lang="ts"> <script lang="ts">
import type { DrawerRootProps, DrawerRootEmits } from 'vaul-vue' import type { DrawerRootProps, DrawerRootEmits } from 'vaul-vue'
import type { DialogContentProps, DialogContentEmits } from 'reka-ui' import type { DialogContentProps } from 'reka-ui'
import type { AppConfig } from '@nuxt/schema' import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config' import _appConfig from '#build/app.config'
import theme from '#build/ui/drawer' import theme from '#build/ui/drawer'
import { tv } from '../utils/tv' import { tv } from '../utils/tv'
import type { EmitsToProps } from '../types/utils'
const appConfigDrawer = _appConfig as AppConfig & { ui: { drawer: Partial<typeof theme> } } const appConfigDrawer = _appConfig as AppConfig & { ui: { drawer: Partial<typeof theme> } }
const drawer = tv({ extend: tv(theme), ...(appConfigDrawer.ui?.drawer || {}) }) const drawer = tv({ extend: tv(theme), ...(appConfigDrawer.ui?.drawer || {}) })
export interface DrawerProps extends Pick<DrawerRootProps, 'activeSnapPoint' | 'closeThreshold' | 'shouldScaleBackground' | 'setBackgroundColorOnScale' | 'scrollLockTimeout' | 'fixed' | 'dismissible' | 'modal' | 'open' | 'defaultOpen' | 'nested' | 'direction' | 'noBodyStyles' | 'handleOnly' | 'preventScrollRestoration' | 'snapPoints'> { export interface DrawerProps extends Pick<DrawerRootProps, 'activeSnapPoint' | 'closeThreshold' | 'defaultOpen' | 'direction' | 'fadeFromIndex' | 'fixed' | 'modal' | 'nested' | 'direction' | 'open' | 'scrollLockTimeout' | 'shouldScaleBackground' | 'snapPoints'> {
/** /**
* The element or component this component should render as. * The element or component this component should render as.
* @defaultValue 'div' * @defaultValue 'div'
@@ -25,7 +24,7 @@ export interface DrawerProps extends Pick<DrawerRootProps, 'activeSnapPoint' | '
*/ */
inset?: boolean inset?: boolean
/** The content of the drawer. */ /** The content of the drawer. */
content?: Omit<DialogContentProps, 'as' | 'asChild' | 'forceMount'> & Partial<EmitsToProps<DialogContentEmits>> content?: Omit<DialogContentProps, 'as' | 'asChild' | 'forceMount'>
/** /**
* Render an overlay behind the drawer. * Render an overlay behind the drawer.
* @defaultValue true * @defaultValue true
@@ -42,9 +41,10 @@ export interface DrawerProps extends Pick<DrawerRootProps, 'activeSnapPoint' | '
*/ */
portal?: boolean portal?: boolean
/** /**
* Index of a `snapPoint` from which the overlay fade should be applied. Defaults to the last snap point. * When `false`, the drawer will not close when clicking outside or pressing escape.
* @defaultValue true
*/ */
fadeFromIndex?: any dismissible?: boolean
class?: any class?: any
ui?: Partial<typeof drawer.slots> ui?: Partial<typeof drawer.slots>
} }
@@ -53,6 +53,7 @@ export interface DrawerEmits extends DrawerRootEmits {}
export interface DrawerSlots { export interface DrawerSlots {
default(props?: {}): any default(props?: {}): any
handle(props?: {}): any
content(props?: {}): any content(props?: {}): any
header(props?: {}): any header(props?: {}): any
title(props?: {}): any title(props?: {}): any
@@ -65,21 +66,19 @@ export interface DrawerSlots {
<script setup lang="ts"> <script setup lang="ts">
import { computed, toRef } from 'vue' import { computed, toRef } from 'vue'
import { useForwardPropsEmits } from 'reka-ui' import { useForwardPropsEmits } from 'reka-ui'
import { DrawerRoot, DrawerTrigger, DrawerPortal, DrawerOverlay, DrawerContent, DrawerTitle, DrawerDescription, DrawerHandle } from 'vaul-vue' import { DrawerRoot, DrawerTrigger, DrawerPortal, DrawerOverlay, DrawerContent, DrawerTitle, DrawerDescription } from 'vaul-vue'
import { reactivePick } from '@vueuse/core' import { reactivePick } from '@vueuse/core'
const props = withDefaults(defineProps<DrawerProps>(), { const props = withDefaults(defineProps<DrawerProps>(), {
direction: 'bottom', direction: 'bottom',
portal: true, portal: true,
overlay: true, overlay: true,
handle: true, handle: true
modal: true,
dismissible: true
}) })
const emits = defineEmits<DrawerEmits>() const emits = defineEmits<DrawerEmits>()
const slots = defineSlots<DrawerSlots>() const slots = defineSlots<DrawerSlots>()
const rootProps = useForwardPropsEmits(reactivePick(props, 'activeSnapPoint', 'closeThreshold', 'shouldScaleBackground', 'setBackgroundColorOnScale', 'scrollLockTimeout', 'fixed', 'dismissible', 'modal', 'open', 'defaultOpen', 'nested', 'direction', 'noBodyStyles', 'handleOnly', 'preventScrollRestoration', 'snapPoints', 'fadeFromIndex'), emits) const rootProps = useForwardPropsEmits(reactivePick(props, 'activeSnapPoint', 'closeThreshold', 'defaultOpen', 'dismissible', 'fadeFromIndex', 'fixed', 'modal', 'nested', 'direction', 'open', 'scrollLockTimeout', 'shouldScaleBackground', 'snapPoints'), emits)
const contentProps = toRef(() => props.content) const contentProps = toRef(() => props.content)
const contentEvents = { const contentEvents = {
closeAutoFocus: (e: Event) => e.preventDefault() closeAutoFocus: (e: Event) => e.preventDefault()
@@ -101,7 +100,9 @@ const ui = computed(() => drawer({
<DrawerOverlay v-if="overlay" :class="ui.overlay({ class: props.ui?.overlay })" /> <DrawerOverlay v-if="overlay" :class="ui.overlay({ class: props.ui?.overlay })" />
<DrawerContent :class="ui.content({ class: [!slots.default && props.class, props.ui?.content] })" v-bind="contentProps" v-on="contentEvents"> <DrawerContent :class="ui.content({ class: [!slots.default && props.class, props.ui?.content] })" v-bind="contentProps" v-on="contentEvents">
<DrawerHandle v-if="handle" :class="ui.handle({ class: props.ui?.handle })" /> <slot name="handle">
<div v-if="handle" :class="ui.handle({ class: props.ui?.handle })" />
</slot>
<slot name="content"> <slot name="content">
<div :class="ui.container({ class: props.ui?.container })"> <div :class="ui.container({ class: props.ui?.container })">

View File

@@ -1,13 +1,12 @@
<!-- 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, DropdownMenuContentEmits, DropdownMenuArrowProps } from 'reka-ui' import type { DropdownMenuRootProps, DropdownMenuRootEmits, DropdownMenuContentProps, DropdownMenuArrowProps } from 'reka-ui'
import type { AppConfig } from '@nuxt/schema' import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config' import _appConfig from '#build/app.config'
import theme from '#build/ui/dropdown-menu' import theme from '#build/ui/dropdown-menu'
import { tv } from '../utils/tv' import { tv } from '../utils/tv'
import type { AvatarProps, KbdProps, LinkProps } from '../types' import type { AvatarProps, KbdProps, LinkProps } from '../types'
import type { DynamicSlots, PartialString, EmitsToProps } from '../types/utils' import type { DynamicSlots, PartialString } from '../types/utils'
const appConfigDropdownMenu = _appConfig as AppConfig & { ui: { dropdownMenu: Partial<typeof theme> } } const appConfigDropdownMenu = _appConfig as AppConfig & { ui: { dropdownMenu: Partial<typeof theme> } }
@@ -23,7 +22,7 @@ export interface DropdownMenuItem extends Omit<LinkProps, 'type' | 'raw' | 'cust
icon?: string icon?: string
color?: DropdownMenuVariants['color'] color?: DropdownMenuVariants['color']
avatar?: AvatarProps avatar?: AvatarProps
content?: Omit<DropdownMenuContentProps, 'as' | 'asChild' | 'forceMount'> & Partial<EmitsToProps<DropdownMenuContentEmits>> content?: Omit<DropdownMenuContentProps, 'as' | 'asChild' | 'forceMount'>
kbds?: KbdProps['value'][] | KbdProps[] kbds?: KbdProps['value'][] | KbdProps[]
/** /**
* The item type. * The item type.
@@ -70,7 +69,7 @@ export interface DropdownMenuProps<T> extends Omit<DropdownMenuRootProps, 'dir'>
* The content of the menu. * The content of the menu.
* @defaultValue { side: 'bottom', sideOffset: 8, collisionPadding: 8 } * @defaultValue { side: 'bottom', sideOffset: 8, collisionPadding: 8 }
*/ */
content?: Omit<DropdownMenuContentProps, 'as' | 'asChild' | 'forceMount'> & Partial<EmitsToProps<DropdownMenuContentEmits>> content?: Omit<DropdownMenuContentProps, 'as' | 'asChild' | 'forceMount'>
/** /**
* Display an arrow alongside the menu. * Display an arrow alongside the menu.
* @defaultValue false * @defaultValue false
@@ -102,7 +101,6 @@ 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">
@@ -150,7 +148,7 @@ const ui = computed(() => dropdownMenu({
:loading-icon="loadingIcon" :loading-icon="loadingIcon"
:external-icon="externalIcon" :external-icon="externalIcon"
> >
<template v-for="(_, name) in proxySlots" #[name]="slotData"> <template v-for="(_, name) in proxySlots" #[name]="slotData: any">
<slot :name="name" v-bind="slotData" /> <slot :name="name" v-bind="slotData" />
</template> </template>

View File

@@ -12,34 +12,14 @@ 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
/**
* If true, schema transformations will be applied to the state on submit.
* @defaultValue `true`
*/
transform?: boolean
class?: any class?: any
transform?: boolean
onSubmit?: ((event: FormSubmitEvent<T>) => void | Promise<void>) | (() => void | Promise<void>) onSubmit?: ((event: FormSubmitEvent<T>) => void | Promise<void>) | (() => void | Promise<void>)
} }
@@ -49,7 +29,7 @@ export interface FormEmits<T extends object> {
} }
export interface FormSlots { export interface FormSlots {
default(props?: { errors: FormError[] }): any default(props?: {}): any
} }
</script> </script>
@@ -89,7 +69,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) && !loading.value) { } else if (props.validateOn?.includes(event.type)) {
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)) {
@@ -141,7 +121,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: err?.name ? inputs.value[err.name]?.id : undefined id: inputs.value[err.name]?.id
})) }))
} }
@@ -179,12 +159,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)
@@ -289,6 +269,6 @@ defineExpose<Form<T>>({
:class="form({ class: props.class })" :class="form({ class: props.class })"
@submit.prevent="onSubmitWrapper" @submit.prevent="onSubmitWrapper"
> >
<slot :errors="errors" /> <slot />
</component> </component>
</template> </template>

View File

@@ -31,12 +31,7 @@ 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>
@@ -68,7 +63,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 && (error.name === props.name || (props.errorPattern && error.name.match(props.errorPattern))))?.message) const error = computed(() => props.error || formErrors?.value?.find(error => 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.

View File

@@ -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 | null>() const [modelValue, modelModifiers] = defineModel<string | number>()
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,19 +111,15 @@ function autoFocus() {
} }
// Custom function to handle the v-model properties // Custom function to handle the v-model properties
function updateInput(value: string | null) { function updateInput(value: string) {
if (modelModifiers.trim) { if (modelModifiers.trim) {
value = value?.trim() ?? null value = value.trim()
} }
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()
} }

View File

@@ -1,14 +1,14 @@
<script lang="ts"> <script lang="ts">
import type { InputHTMLAttributes } from 'vue' import type { InputHTMLAttributes } from 'vue'
import type { VariantProps } from 'tailwind-variants' import type { VariantProps } from 'tailwind-variants'
import type { ComboboxRootProps, ComboboxRootEmits, ComboboxContentProps, ComboboxContentEmits, ComboboxArrowProps, AcceptableValue } from 'reka-ui' import type { ComboboxRootProps, ComboboxRootEmits, ComboboxContentProps, ComboboxArrowProps, AcceptableValue } from 'reka-ui'
import type { AppConfig } from '@nuxt/schema' import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config' import _appConfig from '#build/app.config'
import theme from '#build/ui/input-menu' import theme from '#build/ui/input-menu'
import type { UseComponentIconsProps } from '../composables/useComponentIcons' import type { UseComponentIconsProps } from '../composables/useComponentIcons'
import { tv } from '../utils/tv' import { tv } from '../utils/tv'
import type { AvatarProps, ChipProps, InputProps } from '../types' import type { AvatarProps, ChipProps, InputProps } from '../types'
import type { PartialString, MaybeArrayOfArray, MaybeArrayOfArrayItem, SelectModelValue, SelectModelValueEmits, SelectItemKey, EmitsToProps } from '../types/utils' import type { PartialString, MaybeArrayOfArray, MaybeArrayOfArrayItem, SelectModelValue, SelectModelValueEmits, SelectItemKey } from '../types/utils'
const appConfigInputMenu = _appConfig as AppConfig & { ui: { inputMenu: Partial<typeof theme> } } const appConfigInputMenu = _appConfig as AppConfig & { ui: { inputMenu: Partial<typeof theme> } }
@@ -81,7 +81,7 @@ export interface InputMenuProps<T extends MaybeArrayOfArrayItem<I>, I extends Ma
* The content of the menu. * The content of the menu.
* @defaultValue { side: 'bottom', sideOffset: 8, collisionPadding: 8, position: 'popper' } * @defaultValue { side: 'bottom', sideOffset: 8, collisionPadding: 8, position: 'popper' }
*/ */
content?: Omit<ComboboxContentProps, 'as' | 'asChild' | 'forceMount'> & Partial<EmitsToProps<ComboboxContentEmits>> content?: Omit<ComboboxContentProps, 'as' | 'asChild' | 'forceMount'>
/** /**
* Display an arrow alongside the menu. * Display an arrow alongside the menu.
* @defaultValue false * @defaultValue false

View File

@@ -1,6 +1,4 @@
<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
@@ -8,8 +6,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
target?: LinkProps['target'] rel?: string
rel?: LinkProps['rel'] target?: string
isExternal?: boolean isExternal?: boolean
} }
</script> </script>

View File

@@ -1,11 +1,10 @@
<script lang="ts"> <script lang="ts">
import type { DialogRootProps, DialogRootEmits, DialogContentProps, DialogContentEmits } from 'reka-ui' import type { DialogRootProps, DialogRootEmits, DialogContentProps } from 'reka-ui'
import type { AppConfig } from '@nuxt/schema' import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config' import _appConfig from '#build/app.config'
import theme from '#build/ui/modal' import theme from '#build/ui/modal'
import { tv } from '../utils/tv' import { tv } from '../utils/tv'
import type { ButtonProps } from '../types' import type { ButtonProps } from '../types'
import type { EmitsToProps } from '../types/utils'
const appConfigModal = _appConfig as AppConfig & { ui: { modal: Partial<typeof theme> } } const appConfigModal = _appConfig as AppConfig & { ui: { modal: Partial<typeof theme> } }
@@ -15,7 +14,7 @@ export interface ModalProps extends DialogRootProps {
title?: string title?: string
description?: string description?: string
/** The content of the modal. */ /** The content of the modal. */
content?: Omit<DialogContentProps, 'as' | 'asChild' | 'forceMount'> & Partial<EmitsToProps<DialogContentEmits>> content?: Omit<DialogContentProps, 'as' | 'asChild' | 'forceMount'>
/** /**
* Render an overlay behind the modal. * Render an overlay behind the modal.
* @defaultValue true * @defaultValue true
@@ -98,20 +97,18 @@ const appConfig = useAppConfig()
const rootProps = useForwardPropsEmits(reactivePick(props, 'open', 'defaultOpen', 'modal'), emits) const rootProps = useForwardPropsEmits(reactivePick(props, 'open', 'defaultOpen', 'modal'), emits)
const contentProps = toRef(() => props.content) const contentProps = toRef(() => props.content)
const contentEvents = computed(() => { const contentEvents = computed(() => {
const events = {
closeAutoFocus: (e: Event) => e.preventDefault()
}
if (!props.dismissible) { if (!props.dismissible) {
return { return {
pointerDownOutside: (e: Event) => e.preventDefault(), pointerDownOutside: (e: Event) => e.preventDefault(),
interactOutside: (e: Event) => e.preventDefault(), interactOutside: (e: Event) => e.preventDefault(),
escapeKeyDown: (e: Event) => e.preventDefault(), escapeKeyDown: (e: Event) => e.preventDefault(),
...events closeAutoFocus: (e: Event) => e.preventDefault()
} }
} }
return events return {
closeAutoFocus: (e: Event) => e.preventDefault()
}
}) })
const ui = computed(() => modal({ const ui = computed(() => modal({
@@ -161,7 +158,7 @@ const ui = computed(() => modal({
</DialogDescription> </DialogDescription>
</div> </div>
<DialogClose v-if="close || !!slots.close" as-child> <DialogClose as-child>
<slot name="close" :ui="ui"> <slot name="close" :ui="ui">
<UButton <UButton
v-if="close" v-if="close"

View File

@@ -1,13 +1,12 @@
<!-- 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, NavigationMenuContentEmits, CollapsibleRootProps } from 'reka-ui' import type { NavigationMenuRootProps, NavigationMenuRootEmits, NavigationMenuContentProps, CollapsibleRootProps } from 'reka-ui'
import type { AppConfig } from '@nuxt/schema' import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config' import _appConfig from '#build/app.config'
import theme from '#build/ui/navigation-menu' import theme from '#build/ui/navigation-menu'
import { tv } from '../utils/tv' import { tv } from '../utils/tv'
import type { AvatarProps, BadgeProps, LinkProps } from '../types' import type { AvatarProps, BadgeProps, LinkProps } from '../types'
import type { DynamicSlots, MaybeArrayOfArray, MaybeArrayOfArrayItem, PartialString, EmitsToProps } from '../types/utils' import type { DynamicSlots, MaybeArrayOfArray, MaybeArrayOfArrayItem, PartialString } from '../types/utils'
const appConfigNavigationMenu = _appConfig as AppConfig & { ui: { navigationMenu: Partial<typeof theme> } } const appConfigNavigationMenu = _appConfig as AppConfig & { ui: { navigationMenu: Partial<typeof theme> } }
@@ -94,7 +93,7 @@ export interface NavigationMenuProps<T> extends Pick<NavigationMenuRootProps, 'm
*/ */
highlightColor?: NavigationMenuVariants['highlightColor'] highlightColor?: NavigationMenuVariants['highlightColor']
/** The content of the menu. */ /** The content of the menu. */
content?: Omit<NavigationMenuContentProps, 'as' | 'asChild' | 'forceMount'> & Partial<EmitsToProps<NavigationMenuContentEmits>> content?: Omit<NavigationMenuContentProps, 'as' | 'asChild' | 'forceMount'>
/** /**
* The orientation of the content. * The orientation of the content.
* Only works when `orientation` is `horizontal`. * Only works when `orientation` is `horizontal`.
@@ -126,7 +125,6 @@ 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>">

View File

@@ -1,6 +1,7 @@
<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'
@@ -77,7 +78,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) => ButtonProps['to'] to?: (page: number) => RouteLocationRaw
class?: any class?: any
ui?: Partial<typeof pagination.slots> ui?: Partial<typeof pagination.slots>
} }

View File

@@ -1,4 +1,3 @@
<!-- 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'
@@ -46,7 +45,6 @@ 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">

View File

@@ -1,10 +1,9 @@
<script lang="ts"> <script lang="ts">
import type { PopoverRootProps, HoverCardRootProps, PopoverRootEmits, PopoverContentProps, PopoverContentEmits, PopoverArrowProps } from 'reka-ui' import type { PopoverRootProps, HoverCardRootProps, PopoverRootEmits, PopoverContentProps, PopoverArrowProps } from 'reka-ui'
import type { AppConfig } from '@nuxt/schema' import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config' import _appConfig from '#build/app.config'
import theme from '#build/ui/popover' import theme from '#build/ui/popover'
import { tv } from '../utils/tv' import { tv } from '../utils/tv'
import type { EmitsToProps } from '../types/utils'
const appConfigPopover = _appConfig as AppConfig & { ui: { popover: Partial<typeof theme> } } const appConfigPopover = _appConfig as AppConfig & { ui: { popover: Partial<typeof theme> } }
@@ -20,7 +19,7 @@ export interface PopoverProps extends PopoverRootProps, Pick<HoverCardRootProps,
* The content of the popover. * The content of the popover.
* @defaultValue { side: 'bottom', sideOffset: 8, collisionPadding: 8 } * @defaultValue { side: 'bottom', sideOffset: 8, collisionPadding: 8 }
*/ */
content?: Omit<PopoverContentProps, 'as' | 'asChild' | 'forceMount'> & Partial<EmitsToProps<PopoverContentEmits>> content?: Omit<PopoverContentProps, 'as' | 'asChild' | 'forceMount'>
/** /**
* Display an arrow alongside the popover. * Display an arrow alongside the popover.
* @defaultValue false * @defaultValue false

View File

@@ -161,8 +161,8 @@ function onUpdate(value: any) {
<RadioGroupItem <RadioGroupItem
:id="item.id" :id="item.id"
:value="item.value" :value="item.value"
:disabled="item.disabled" :disabled="disabled"
:class="ui.base({ class: props.ui?.base, disabled: item.disabled })" :class="ui.base({ class: props.ui?.base })"
> >
<RadioGroupIndicator :class="ui.indicator({ class: props.ui?.indicator })" /> <RadioGroupIndicator :class="ui.indicator({ class: props.ui?.indicator })" />
</RadioGroupItem> </RadioGroupItem>

View File

@@ -1,13 +1,13 @@
<script lang="ts"> <script lang="ts">
import type { VariantProps } from 'tailwind-variants' import type { VariantProps } from 'tailwind-variants'
import type { SelectRootProps, SelectRootEmits, SelectContentProps, SelectContentEmits, SelectArrowProps, AcceptableValue } from 'reka-ui' import type { SelectRootProps, SelectRootEmits, SelectContentProps, SelectArrowProps, AcceptableValue } from 'reka-ui'
import type { AppConfig } from '@nuxt/schema' import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config' import _appConfig from '#build/app.config'
import theme from '#build/ui/select' import theme from '#build/ui/select'
import type { UseComponentIconsProps } from '../composables/useComponentIcons' import type { UseComponentIconsProps } from '../composables/useComponentIcons'
import { tv } from '../utils/tv' import { tv } from '../utils/tv'
import type { AvatarProps, ChipProps, InputProps } from '../types' import type { AvatarProps, ChipProps, InputProps } from '../types'
import type { PartialString, MaybeArrayOfArray, MaybeArrayOfArrayItem, SelectModelValue, SelectModelValueEmits, SelectItemKey, EmitsToProps } from '../types/utils' import type { PartialString, MaybeArrayOfArray, MaybeArrayOfArrayItem, SelectModelValue, SelectModelValueEmits, SelectItemKey } from '../types/utils'
const appConfigSelect = _appConfig as AppConfig & { ui: { select: Partial<typeof theme> } } const appConfigSelect = _appConfig as AppConfig & { ui: { select: Partial<typeof theme> } }
@@ -64,7 +64,7 @@ export interface SelectProps<T extends MaybeArrayOfArrayItem<I>, I extends Maybe
* The content of the menu. * The content of the menu.
* @defaultValue { side: 'bottom', sideOffset: 8, collisionPadding: 8, position: 'popper' } * @defaultValue { side: 'bottom', sideOffset: 8, collisionPadding: 8, position: 'popper' }
*/ */
content?: Omit<SelectContentProps, 'as' | 'asChild' | 'forceMount'> & Partial<EmitsToProps<SelectContentEmits>> content?: Omit<SelectContentProps, 'as' | 'asChild' | 'forceMount'>
/** /**
* Display an arrow alongside the menu. * Display an arrow alongside the menu.
* @defaultValue false * @defaultValue false

View File

@@ -1,13 +1,13 @@
<script lang="ts"> <script lang="ts">
import type { VariantProps } from 'tailwind-variants' import type { VariantProps } from 'tailwind-variants'
import type { ComboboxRootProps, ComboboxRootEmits, ComboboxContentProps, ComboboxContentEmits, ComboboxArrowProps, AcceptableValue } from 'reka-ui' import type { ComboboxRootProps, ComboboxRootEmits, ComboboxContentProps, ComboboxArrowProps, AcceptableValue } from 'reka-ui'
import type { AppConfig } from '@nuxt/schema' import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config' import _appConfig from '#build/app.config'
import theme from '#build/ui/select-menu' import theme from '#build/ui/select-menu'
import type { UseComponentIconsProps } from '../composables/useComponentIcons' import type { UseComponentIconsProps } from '../composables/useComponentIcons'
import { tv } from '../utils/tv' import { tv } from '../utils/tv'
import type { AvatarProps, ChipProps, InputProps } from '../types' import type { AvatarProps, ChipProps, InputProps } from '../types'
import type { PartialString, MaybeArrayOfArray, MaybeArrayOfArrayItem, SelectModelValue, SelectModelValueEmits, SelectItemKey, EmitsToProps } from '../types/utils' import type { PartialString, MaybeArrayOfArray, MaybeArrayOfArrayItem, SelectModelValue, SelectModelValueEmits, SelectItemKey } from '../types/utils'
const appConfigSelectMenu = _appConfig as AppConfig & { ui: { selectMenu: Partial<typeof theme> } } const appConfigSelectMenu = _appConfig as AppConfig & { ui: { selectMenu: Partial<typeof theme> } }
@@ -72,7 +72,7 @@ export interface SelectMenuProps<T extends MaybeArrayOfArrayItem<I>, I extends M
* The content of the menu. * The content of the menu.
* @defaultValue { side: 'bottom', sideOffset: 8, collisionPadding: 8, position: 'popper' } * @defaultValue { side: 'bottom', sideOffset: 8, collisionPadding: 8, position: 'popper' }
*/ */
content?: Omit<ComboboxContentProps, 'as' | 'asChild' | 'forceMount'> & Partial<EmitsToProps<ComboboxContentEmits>> content?: Omit<ComboboxContentProps, 'as' | 'asChild' | 'forceMount'>
/** /**
* Display an arrow alongside the menu. * Display an arrow alongside the menu.
* @defaultValue false * @defaultValue false

View File

@@ -1,12 +1,11 @@
<script lang="ts"> <script lang="ts">
import type { VariantProps } from 'tailwind-variants' import type { VariantProps } from 'tailwind-variants'
import type { DialogRootProps, DialogRootEmits, DialogContentProps, DialogContentEmits } from 'reka-ui' import type { DialogRootProps, DialogRootEmits, DialogContentProps } from 'reka-ui'
import type { AppConfig } from '@nuxt/schema' import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config' import _appConfig from '#build/app.config'
import theme from '#build/ui/slideover' import theme from '#build/ui/slideover'
import { tv } from '../utils/tv' import { tv } from '../utils/tv'
import type { ButtonProps } from '../types' import type { ButtonProps } from '../types'
import type { EmitsToProps } from '../types/utils'
const appConfigSlideover = _appConfig as AppConfig & { ui: { slideover: Partial<typeof theme> } } const appConfigSlideover = _appConfig as AppConfig & { ui: { slideover: Partial<typeof theme> } }
@@ -18,7 +17,7 @@ export interface SlideoverProps extends DialogRootProps {
title?: string title?: string
description?: string description?: string
/** The content of the slideover. */ /** The content of the slideover. */
content?: Omit<DialogContentProps, 'as' | 'asChild' | 'forceMount'> & Partial<EmitsToProps<DialogContentEmits>> content?: Omit<DialogContentProps, 'as' | 'asChild' | 'forceMount'>
/** /**
* Render an overlay behind the slideover. * Render an overlay behind the slideover.
* @defaultValue true * @defaultValue true
@@ -102,20 +101,18 @@ const appConfig = useAppConfig()
const rootProps = useForwardPropsEmits(reactivePick(props, 'open', 'defaultOpen', 'modal'), emits) const rootProps = useForwardPropsEmits(reactivePick(props, 'open', 'defaultOpen', 'modal'), emits)
const contentProps = toRef(() => props.content) const contentProps = toRef(() => props.content)
const contentEvents = computed(() => { const contentEvents = computed(() => {
const events = {
closeAutoFocus: (e: Event) => e.preventDefault()
}
if (!props.dismissible) { if (!props.dismissible) {
return { return {
pointerDownOutside: (e: Event) => e.preventDefault(), pointerDownOutside: (e: Event) => e.preventDefault(),
interactOutside: (e: Event) => e.preventDefault(), interactOutside: (e: Event) => e.preventDefault(),
escapeKeyDown: (e: Event) => e.preventDefault(), escapeKeyDown: (e: Event) => e.preventDefault(),
...events closeAutoFocus: (e: Event) => e.preventDefault()
} }
} }
return events return {
closeAutoFocus: (e: Event) => e.preventDefault()
}
}) })
const ui = computed(() => slideover({ const ui = computed(() => slideover({
@@ -165,7 +162,7 @@ const ui = computed(() => slideover({
</DialogDescription> </DialogDescription>
</div> </div>
<DialogClose v-if="close || !!slots.close" as-child> <DialogClose as-child>
<slot name="close" :ui="ui"> <slot name="close" :ui="ui">
<UButton <UButton
v-if="close" v-if="close"

View File

@@ -1,4 +1,3 @@
<!-- 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 { StepperRootProps, StepperRootEmits } from 'reka-ui' import type { StepperRootProps, StepperRootEmits } from 'reka-ui'
@@ -69,7 +68,6 @@ export type StepperSlots<T extends StepperItem> = {
description: SlotProps<T> description: SlotProps<T>
content: SlotProps<T> content: SlotProps<T>
} & DynamicSlots<T, SlotProps<T>> } & DynamicSlots<T, SlotProps<T>>
</script> </script>
<script setup lang="ts" generic="T extends StepperItem"> <script setup lang="ts" generic="T extends StepperItem">

View File

@@ -289,7 +289,7 @@ function handleRowSelect(row: TableRow<T>, e: Event) {
return return
} }
const target = e.target as HTMLElement const target = e.target as HTMLElement
const isInteractive = target.closest('button') || target.closest('a') const isInteractive = target.closest('button')
if (isInteractive) { if (isInteractive) {
return return
} }

View File

@@ -1,4 +1,3 @@
<!-- 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 { TabsRootProps, TabsRootEmits } from 'reka-ui' import type { TabsRootProps, TabsRootEmits } from 'reka-ui'
@@ -77,7 +76,6 @@ export type TabsSlots<T extends { slot?: string }> = {
trailing: SlotProps<T> trailing: SlotProps<T>
content: SlotProps<T> content: SlotProps<T>
} & DynamicSlots<T, SlotProps<T>> } & DynamicSlots<T, SlotProps<T>>
</script> </script>
<script setup lang="ts" generic="T extends TabsItem"> <script setup lang="ts" generic="T extends TabsItem">

View File

@@ -74,7 +74,7 @@ const props = withDefaults(defineProps<TextareaProps>(), {
defineSlots<TextareaSlots>() defineSlots<TextareaSlots>()
const emits = defineEmits<TextareaEmits>() const emits = defineEmits<TextareaEmits>()
const [modelValue, modelModifiers] = defineModel<string | number | null>() const [modelValue, modelModifiers] = defineModel<string | number>()
const { emitFormFocus, emitFormBlur, emitFormInput, emitFormChange, size, color, id, name, highlight, disabled, ariaAttrs } = useFormField<TextareaProps>(props, { deferInputValidation: true }) const { emitFormFocus, emitFormBlur, emitFormInput, emitFormChange, size, color, id, name, highlight, disabled, ariaAttrs } = useFormField<TextareaProps>(props, { deferInputValidation: true })
@@ -94,19 +94,15 @@ function autoFocus() {
} }
// Custom function to handle the v-model properties // Custom function to handle the v-model properties
function updateInput(value: string | null) { function updateInput(value: string) {
if (modelModifiers.trim) { if (modelModifiers.trim) {
value = value?.trim() ?? null value = value.trim()
} }
if (modelModifiers.number) { if (modelModifiers.number) {
value = looseToNumber(value) value = looseToNumber(value)
} }
if (modelModifiers.nullify) {
value ||= null
}
modelValue.value = value modelValue.value = value
emitFormInput() emitFormInput()
} }

View File

@@ -168,7 +168,7 @@ defineExpose({
</slot> </slot>
</template> </template>
<ToastClose v-if="close || !!slots.close" as-child> <ToastClose as-child>
<slot name="close" :ui="ui"> <slot name="close" :ui="ui">
<UButton <UButton
v-if="close" v-if="close"

View File

@@ -1,11 +1,10 @@
<script lang="ts"> <script lang="ts">
import type { TooltipRootProps, TooltipRootEmits, TooltipContentProps, TooltipContentEmits, TooltipArrowProps } from 'reka-ui' import type { TooltipRootProps, TooltipRootEmits, TooltipContentProps, TooltipArrowProps } from 'reka-ui'
import type { AppConfig } from '@nuxt/schema' import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config' import _appConfig from '#build/app.config'
import theme from '#build/ui/tooltip' import theme from '#build/ui/tooltip'
import { tv } from '../utils/tv' import { tv } from '../utils/tv'
import type { KbdProps } from '../types' import type { KbdProps } from '../types'
import type { EmitsToProps } from '../types/utils'
const appConfigTooltip = _appConfig as AppConfig & { ui: { tooltip: Partial<typeof theme> } } const appConfigTooltip = _appConfig as AppConfig & { ui: { tooltip: Partial<typeof theme> } }
@@ -20,7 +19,7 @@ export interface TooltipProps extends TooltipRootProps {
* The content of the tooltip. * The content of the tooltip.
* @defaultValue { side: 'bottom', sideOffset: 8, collisionPadding: 8 } * @defaultValue { side: 'bottom', sideOffset: 8, collisionPadding: 8 }
*/ */
content?: Omit<TooltipContentProps, 'as' | 'asChild'> & Partial<EmitsToProps<TooltipContentEmits>> content?: Omit<TooltipContentProps, 'as' | 'asChild'>
/** /**
* Display an arrow alongside the tooltip. * Display an arrow alongside the tooltip.
* @defaultValue false * @defaultValue false

View File

@@ -1,4 +1,3 @@
<!-- 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 { TreeRootProps, TreeRootEmits } from 'reka-ui' import type { TreeRootProps, TreeRootEmits } from 'reka-ui'
@@ -98,7 +97,6 @@ export type TreeSlots<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 TreeItem, M extends boolean = false, K extends SelectItemKey<T> | undefined = undefined"> <script setup lang="ts" generic="T extends TreeItem, M extends boolean = false, K extends SelectItemKey<T> | undefined = undefined">

View File

@@ -1,14 +1,13 @@
import { defu } from 'defu' import { defu } from 'defu'
import type { Locale, Direction } from '../types/locale' import type { Locale, Direction, Messages } from '../types/locale'
interface DefineLocaleOptions<M> { interface DefineLocaleOptions {
name: string name: string
code: string code: string
dir?: Direction dir?: Direction
messages: M messages: Messages
} }
/* @__NO_SIDE_EFFECTS__ */ export function defineLocale(options: DefineLocaleOptions): Locale {
export function defineLocale<M>(options: DefineLocaleOptions<M>): Locale<M> { return defu<DefineLocaleOptions, [{ dir: Direction }]>(options, { dir: 'ltr' })
return defu<DefineLocaleOptions<M>, [{ dir: Direction }]>(options, { dir: 'ltr' })
} }

View File

@@ -59,7 +59,6 @@ export function extractShortcuts(items: any[] | any[][]) {
return shortcuts return shortcuts
} }
/* @__NO_SIDE_EFFECTS__ */
export function defineShortcuts(config: MaybeRef<ShortcutsConfig>, options: ShortcutsOptions = {}) { export function defineShortcuts(config: MaybeRef<ShortcutsConfig>, options: ShortcutsOptions = {}) {
const chainedInputs = ref<string[]>([]) const chainedInputs = ref<string[]>([])
const clearChainedInput = () => { const clearChainedInput = () => {

View File

@@ -67,4 +67,4 @@ const _useKbd = () => {
} }
} }
export const useKbd = /* @__PURE__ */ createSharedComposable(_useKbd) export const useKbd = createSharedComposable(_useKbd)

Some files were not shown because too many files have changed in this diff Show More