Compare commits

..

5 Commits

Author SHA1 Message Date
rdjanuar
21939ed333 docs(SelectMenu): add section customization on clearable prop 2024-11-12 19:04:42 +07:00
rdjanuar
5a414eb55a feat(SelectMenu): add clearble 2024-11-12 18:47:39 +07:00
kyyy
3a5960fb58 Merge branch 'dev' into issue-1057 2024-11-12 17:17:46 +07:00
rdjanuar
a78203ce49 up 2024-11-08 14:30:53 +07:00
rdjanuar
592da565fe feat(SelectMenu): add clearble 2024-11-08 14:02:54 +07:00
146 changed files with 7504 additions and 8077 deletions

1
.github/CODEOWNERS vendored
View File

@@ -1 +0,0 @@
* @benjamincanac

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

@@ -29,20 +29,11 @@ body:
- Build Modules: `-` - Build Modules: `-`
validations: validations:
required: true required: true
- type: dropdown
id: package
attributes:
label: Is this bug related to Nuxt or Vue?
options:
- Nuxt
- Vue
validations:
required: true
- type: input - type: input
id: version id: version
attributes: attributes:
label: Version label: Version
placeholder: v3.0.0-alpha.x placeholder: v3.0.0-alpha.5
validations: validations:
required: true required: true
- type: textarea - type: textarea

View File

@@ -5,14 +5,14 @@ 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: dropdown - type: dropdown
id: version id: version
attributes: attributes:
label: For what version of Nuxt UI are you suggesting this? label: For what version of Nuxt UI are you suggesting this?
options: options:
- v2.x - v2.x
- v3.0.0-alpha.x - v3-alpha
validations: validations:
required: true required: true
- type: textarea - type: textarea

View File

@@ -5,14 +5,14 @@ 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: dropdown - type: dropdown
id: version id: version
attributes: attributes:
label: For what version of Nuxt UI are you asking this question? label: For what version of Nuxt UI are you asking this question?
options: options:
- v2.x - v2.x
- v3.0.0-alpha.x - v3-alpha
validations: validations:
required: true required: true
- type: textarea - type: textarea

View File

@@ -1,15 +1,15 @@
name: module name: ci-dev
on: on:
push: push:
branches: branches:
- v2 - dev
pull_request: pull_request:
branches: branches:
- v2 - dev
jobs: jobs:
build: ci:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
permissions: permissions:
@@ -19,7 +19,7 @@ jobs:
strategy: strategy:
matrix: matrix:
os: [ubuntu-latest] # macos-latest, windows-latest os: [ubuntu-latest] # macos-latest, windows-latest
node: [22] node: [20]
env: env:
NUXT_GITHUB_TOKEN: ${{ secrets.NUXT_GITHUB_TOKEN }} NUXT_GITHUB_TOKEN: ${{ secrets.NUXT_GITHUB_TOKEN }}
@@ -37,6 +37,16 @@ jobs:
node-version: ${{ matrix.node }} node-version: ${{ matrix.node }}
cache: pnpm cache: pnpm
- name: Filter changes
uses: dorny/paths-filter@v3
id: changes
with:
filters: |
src:
- 'src/**'
- 'package.json'
- 'pnpm-lock.yaml'
- name: Install dependencies - name: Install dependencies
run: pnpm install run: pnpm install
@@ -55,5 +65,8 @@ jobs:
- name: Test - name: Test
run: pnpm run test run run: pnpm run test run
- name: Publish - name: Release Edge
run: pnpx pkg-pr-new publish --compact --no-template --pnpm if: github.event_name == 'push' && steps.changes.outputs.src == 'true'
run: ./scripts/release-edge.sh
env:
NODE_AUTH_TOKEN: ${{secrets.NODE_AUTH_TOKEN}}

View File

@@ -1,18 +1,18 @@
name: release name: ci-main
on: on:
push: push:
tags: branches:
- 'v2*' - main
jobs: jobs:
publish: ci:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
matrix: matrix:
os: [ubuntu-latest] # macos-latest, windows-latest os: [ubuntu-latest] # macos-latest, windows-latest
node: [22] node: [20]
env: env:
NUXT_GITHUB_TOKEN: ${{ secrets.NUXT_GITHUB_TOKEN }} NUXT_GITHUB_TOKEN: ${{ secrets.NUXT_GITHUB_TOKEN }}
@@ -48,7 +48,14 @@ jobs:
- name: Test - name: Test
run: pnpm run test run run: pnpm run test run
- name: Publish - name: Version Check
id: check
uses: EndBug/version-check@v2
with:
token: ${{ secrets.GITHUB_TOKEN }}
- name: Release
if: github.event_name == 'push' && steps.check.outputs.changed == 'true'
run: ./scripts/release.sh run: ./scripts/release.sh
env: env:
NODE_AUTH_TOKEN: ${{ secrets.NODE_AUTH_TOKEN }} NODE_AUTH_TOKEN: ${{ secrets.NODE_AUTH_TOKEN }}

23
.github/workflows/stale.yml vendored Normal file
View File

@@ -0,0 +1,23 @@
name: stale
on:
schedule:
- cron: '30 1 * * *'
jobs:
stale:
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
steps:
- uses: actions/stale@v9
with:
exempt-issue-labels: triage,v3
stale-issue-message: 'This issue is stale because it has been open for 30 days with no activity.'
stale-issue-label: stale
stale-pr-label: stale
days-before-stale: 30
days-before-close: -1

View File

@@ -1,85 +1,5 @@
# Changelog # Changelog
## [2.22.1](https://github.com/nuxt/ui/compare/v2.22.0...v2.22.1) (2025-07-16)
### Bug Fixes
* **Badge/Button:** support numeric zero as visible label ([#4116](https://github.com/nuxt/ui/issues/4116)) ([2971a31](https://github.com/nuxt/ui/commit/2971a3124299e927ddb506bb0fc61b906aa0cfeb))
## [2.22.0](https://github.com/nuxt/ui/compare/v2.21.1...v2.22.0) (2025-04-22)
### ⚠ BREAKING CHANGES
* **Form:** drop explicit support for `zod` and `valibot` (#3618)
### Bug Fixes
* **Link:** properly pick all `aria-*` & `data-*` attrs ([2bef1e2](https://github.com/nuxt/ui/commit/2bef1e26c6dfd5ee81b11f6da76e257861fc0bef)), closes [#3007](https://github.com/nuxt/ui/issues/3007)
* **Table:** checkbox still emit `[@select](https://github.com/select)` event ([#3269](https://github.com/nuxt/ui/issues/3269)) ([c0e14d0](https://github.com/nuxt/ui/commit/c0e14d006ea39965e805adbf9698f5cb95e7c965))
* **Table:** remove type annotation in template ([4e96dcc](https://github.com/nuxt/ui/commit/4e96dcca4213bbb56f1dd465ad7d47374e83bc9a)), closes [#3146](https://github.com/nuxt/ui/issues/3146)
### Code Refactoring
* **Form:** drop explicit support for `zod` and `valibot` ([#3618](https://github.com/nuxt/ui/issues/3618)) ([ee37362](https://github.com/nuxt/ui/commit/ee373629d5a9800921301dcbab309f884eaf83fb))
## [2.21.1](https://github.com/nuxt/ui/compare/v2.21.0...v2.21.1) (2025-03-08)
### Features
* **Form:** add standard schema support ([#2880](https://github.com/nuxt/ui/issues/2880)) ([9c36d37](https://github.com/nuxt/ui/commit/9c36d37b847468d1cbd76eea38ac00cbc22549ca))
* **module:** add `colorMode` option ([d2ceead](https://github.com/nuxt/ui/commit/d2ceeadae796254128697d94a3e317234bc2ecda)), closes [#3143](https://github.com/nuxt/ui/issues/3143)
* **SelectMenu:** add inputTargetForm prop to handle input validation ([#3107](https://github.com/nuxt/ui/issues/3107)) ([feb716c](https://github.com/nuxt/ui/commit/feb716c941f1e7315009b53861a4dc0c2f233052))
### Bug Fixes
* **Alert/Notification:** allow description ui override ([125a281](https://github.com/nuxt/ui/commit/125a28190b1a83e2456457e7a4ec618384b2446c)), closes [#2554](https://github.com/nuxt/ui/issues/2554)
* **Table:** revert [#2600](https://github.com/nuxt/ui/issues/2600) to fix excessive column data slot re-renders ([#3375](https://github.com/nuxt/ui/issues/3375)) ([23d9b51](https://github.com/nuxt/ui/commit/23d9b51a5861f5d1f32f68a3141a600655a0598a))
## [2.21.0](https://github.com/nuxt/ui/compare/v2.20.0...v2.21.0) (2025-01-14)
### Features
* **module:** handle `tailwindMerge` config from `app.config` ([#2902](https://github.com/nuxt/ui/issues/2902)) ([ea15e21](https://github.com/nuxt/ui/commit/ea15e21cdcba00e21302415829113e8c6def8a6e))
### Bug Fixes
* **Table:** `v-model` causing first column missing ([#2890](https://github.com/nuxt/ui/issues/2890)) ([d573fb6](https://github.com/nuxt/ui/commit/d573fb636f7f749ce95b93c5fb1ae2a053eeeeb0))
* **Table:** remove `[@select](https://github.com/select)` event on checkbox ([#3042](https://github.com/nuxt/ui/issues/3042)) ([d9d4f19](https://github.com/nuxt/ui/commit/d9d4f1915aac586ae1abf3ebe67ca9aff65b9be0))
* **tailwind:** use mjs template ([#2945](https://github.com/nuxt/ui/issues/2945)) ([8cf9f27](https://github.com/nuxt/ui/commit/8cf9f27d537bad5ffe4e136f52ff71548a451c5f))
## [2.20.0](https://github.com/nuxt/ui/compare/v2.19.2...v2.20.0) (2024-12-09)
### ⚠ BREAKING CHANGES
* **Form:** resolve async validation in yup & issue directly mutate state (#2701)
### Features
* **Accordion:** add `close` event ([#2750](https://github.com/nuxt/ui/issues/2750)) ([419a24f](https://github.com/nuxt/ui/commit/419a24f7034cefda2c6669f3c26742552e500f63))
* **Badge:** handle `icon` prop ([#2594](https://github.com/nuxt/ui/issues/2594)) ([0d1a76e](https://github.com/nuxt/ui/commit/0d1a76e3c69e08534abb295b96548e67cfbea00c))
* **InputMenu/SelectMenu:** add support for `dot notation` in `by` prop ([#2607](https://github.com/nuxt/ui/issues/2607)) ([53df9d9](https://github.com/nuxt/ui/commit/53df9d9a8cd6850803bdafc7ef6efe4e7404d334))
* **Link:** allow partial query match for `activeClass` ([#2663](https://github.com/nuxt/ui/issues/2663)) ([03e24f4](https://github.com/nuxt/ui/commit/03e24f45836bdddd94b30cbaecc2288a78b56b0b))
* **Notification:** add `pauseTimeoutOnHover` prop ([#2661](https://github.com/nuxt/ui/issues/2661)) ([11b8c3d](https://github.com/nuxt/ui/commit/11b8c3d9db1ec62b1c3557703c7ab5c99cb42df5))
* **Table:** add contextmenu handling to table rows ([#2283](https://github.com/nuxt/ui/issues/2283)) ([c9e6256](https://github.com/nuxt/ui/commit/c9e6256e7f2c06da8bfda13700f56f6994e76eab))
* **Table:** add custom `[@select](https://github.com/select):all` event ([#2581](https://github.com/nuxt/ui/issues/2581)) ([ac323c4](https://github.com/nuxt/ui/commit/ac323c4cccd930f2cd8c1f54b325bd509acd40bf))
* **Table:** allow dynamically render `checkbox` ([#2549](https://github.com/nuxt/ui/issues/2549)) ([d6daf46](https://github.com/nuxt/ui/commit/d6daf466ace42b828151c45b18cd47179e85d66d))
### Bug Fixes
* **AvatarGroup/ButtonGroup/MeterGroup:** allow deeply partial `ui` config ([#2542](https://github.com/nuxt/ui/issues/2542)) ([bf58086](https://github.com/nuxt/ui/commit/bf580863af11d6a1a4c6c6774b44ec37b082e933))
* **Carousel:** wrong `ui` type with `strategy` ([07ef771](https://github.com/nuxt/ui/commit/07ef771b17c72e275508a273371454a5e8a62257))
* **components:** replace `as const` with correct type in config ([#2652](https://github.com/nuxt/ui/issues/2652)) ([51c8b8e](https://github.com/nuxt/ui/commit/51c8b8e3e59d7eceff72625650a199fcf7c6feca))
* **date-picker:** undefined `dayIndex` ([#2545](https://github.com/nuxt/ui/issues/2545)) ([ce955d2](https://github.com/nuxt/ui/commit/ce955d24f1dfd222e87ce88428c0612c3f13cd50))
* **Form:** resolve async validation in yup & issue directly mutate state ([#2701](https://github.com/nuxt/ui/issues/2701)) ([f3632dd](https://github.com/nuxt/ui/commit/f3632ddee511f0fccb24d4fc37403421e84ffdae))
* **Form:** use parsed value from `joi` instead of original state ([#2587](https://github.com/nuxt/ui/issues/2587)) ([acecff4](https://github.com/nuxt/ui/commit/acecff40ec0156e45b4934c5d10c4dfa7c135f8e))
* **InputMenu/SelectMenu:** use `by` prop to compare objects & support dot notation in `value-attribute` ([#2566](https://github.com/nuxt/ui/issues/2566)) ([7154254](https://github.com/nuxt/ui/commit/7154254ac22830f651ec200f7f3af2f5577f2de0))
* **Link:** `exactQuery` prop type ([#2781](https://github.com/nuxt/ui/issues/2781)) ([4cde571](https://github.com/nuxt/ui/commit/4cde571e387775a9b12759f6f8c99117c84cbcff))
* **Notification:** element renders even when no `notification` is present ([#2561](https://github.com/nuxt/ui/issues/2561)) ([d4e408c](https://github.com/nuxt/ui/commit/d4e408cfd8e2ef26021519f2f30f57e9120e1939))
* **Table:** data outdated when rows change ([#2600](https://github.com/nuxt/ui/issues/2600)) ([b23f2de](https://github.com/nuxt/ui/commit/b23f2decfc9607555a315d0d087d0a042f03a938))
* **Table:** missing type on props `loadingState` ([#2551](https://github.com/nuxt/ui/issues/2551)) ([6e66990](https://github.com/nuxt/ui/commit/6e66990372ef6bd7c109a64c753d9b50e96a450b))
* **Table:** prevent `onClick` while blocking element ([#2592](https://github.com/nuxt/ui/issues/2592)) ([9703786](https://github.com/nuxt/ui/commit/97037864b39749db228fa5f51981f19e4a9c29dd))
* **types:** improve `DeepPartial` type for App Config ([#2621](https://github.com/nuxt/ui/issues/2621)) ([976b03f](https://github.com/nuxt/ui/commit/976b03f241ef9626a6338685e43c844a8b3953fd))
## [2.19.2](https://github.com/nuxt/ui/compare/v2.19.1...v2.19.2) (2024-11-05) ## [2.19.2](https://github.com/nuxt/ui/compare/v2.19.1...v2.19.2) (2024-11-05)
### Bug Fixes ### Bug Fixes

View File

@@ -1,4 +1,4 @@
[![nuxt-ui.png](https://volta.s3.fr-par.scw.cloud/nuxt_ui_social_card_531d133fa2.png)](https://ui2.nuxt.com) [![nuxt-ui.png](https://repository-images.githubusercontent.com/428329515/43fec891-9030-4601-8233-5d45ba5c6013)](https://ui.nuxt.com)
# Nuxt UI # Nuxt UI
@@ -22,32 +22,27 @@ Its goal is to provide everything related to UI when building a Nuxt app. This i
- Fully typed - Fully typed
- [Figma Kit](https://www.figma.com/community/file/1436401057300493073) - [Figma Kit](https://www.figma.com/community/file/1436401057300493073)
Read more on [ui2.nuxt.com](https://ui2.nuxt.com) Read more on [ui.nuxt.com](https://ui.nuxt.com)
## Installation ## Installation
```bash ```bash
# npm npx nuxi@latest module add ui
npm install @nuxt/ui@2
# yarn
yarn add @nuxt/ui@2
# pnpm
pnpm add @nuxt/ui@2
# bun
bun add @nuxt/ui@2
``` ```
Next, register the `@nuxt/ui` module in your `nuxt.config.ts`: If you want latest updates, please use `@nuxt/ui-edge` in your `package.json`:
```ts ```json
export default defineNuxtConfig({ {
modules: ['@nuxt/ui'] "devDependencies": {
}) "@nuxt/ui": "npm:@nuxt/ui-edge@latest"
}
}
``` ```
## Documentation ## Documentation
Visit https://ui2.nuxt.com to explore the documentation. Visit https://ui.nuxt.com to explore the documentation.
## Credits ## Credits
@@ -64,15 +59,15 @@ Visit https://ui2.nuxt.com to explore the documentation.
Thank you for considering contributing to Nuxt UI. Here are a few ways you can get involved: Thank you for considering contributing to Nuxt UI. Here are a few ways you can get involved:
- Reporting Bugs: If you come across any bugs or issues, please check out the reporting bugs guide to learn how to submit a bug report. - Reporting Bugs: If you come across any bugs or issues, please check out the reporting bugs guide to learn how to submit a bug report.
- Suggestions: Have any thoughts to enhance Nuxt UI? We'd love to hear them! Check out the [contribution guide](https://ui2.nuxt.com/getting-started/contributing) to share your suggestions. - Suggestions: Have any thoughts to enhance Nuxt UI? We'd love to hear them! Check out the [contribution guide](https://ui.nuxt.com/getting-started/contributing) to share your suggestions.
## Local Development ## Local Development
Follow the docs to [Set up your local development environment](https://ui2.nuxt.com/getting-started/contributing#_2-local-development-setup) and contribute. Follow the docs to [Set up your local development environment](https://ui.nuxt.com/getting-started/contributing#_2-local-development-setup) and contribute.
## License ## License
Licensed under the [MIT license](https://github.com/nuxt/ui/blob/v2/LICENSE.md). Licensed under the [MIT license](https://github.com/nuxt/ui/blob/dev/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/latest.svg?style=flat&colorA=18181B&colorB=28CF8D

View File

@@ -91,7 +91,7 @@ useHead({
], ],
link: [ link: [
{ rel: 'icon', type: 'image/svg+xml', href: '/icon.svg' }, { rel: 'icon', type: 'image/svg+xml', href: '/icon.svg' },
{ rel: 'canonical', href: `https://ui2.nuxt.com${withoutTrailingSlash(route.path)}` } { rel: 'canonical', href: `https://ui.nuxt.com${withoutTrailingSlash(route.path)}` }
], ],
htmlAttrs: { htmlAttrs: {
lang: 'en' lang: 'en'

View File

@@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
const id = 'nuxt-ui-banner-4' const id = 'nuxt-ui-banner-2'
const to = 'https://ui.nuxt.com' const to = 'https://ui3.nuxt.dev'
const hideBanner = () => { const hideBanner = () => {
localStorage.setItem(id, 'true') localStorage.setItem(id, 'true')
@@ -30,6 +30,7 @@ if (import.meta.server) {
:to="to" :to="to"
target="_blank" target="_blank"
class="focus:outline-none" class="focus:outline-none"
aria-label="Nuxt UI Pro pricing"
tabindex="-1" tabindex="-1"
> >
<span class="absolute inset-0 " aria-hidden="true" /> <span class="absolute inset-0 " aria-hidden="true" />
@@ -39,19 +40,19 @@ if (import.meta.server) {
<div class="lg:flex-1 hidden lg:flex items-center" /> <div class="lg:flex-1 hidden lg:flex items-center" />
<p class="text-sm font-medium text-white dark:text-gray-900 truncate"> <p class="text-sm font-medium text-white dark:text-gray-900 truncate">
<UIcon name="i-lucide-rocket" class="size-5 align-top flex-shrink-0 pointer-events-none mr-1.5" /> <UIcon name="i-heroicons-rocket-launch" class="w-5 h-5 align-top flex-shrink-0 pointer-events-none mr-2" />
<span class="font-bold">Nuxt UI v3</span> is officially released! <span class="font-semibold">Nuxt UI v3-alpha</span> has been released!
</p> </p>
<!-- <UButton <UButton
:to="to" to="https://ui3.nuxt.dev"
target="_blank" target="_blank"
label="Buy now" label="Try it out"
color="black" color="black"
variant="solid" variant="solid"
size="2xs" size="2xs"
trailing-icon="i-heroicons-arrow-right-20-solid" trailing-icon="i-heroicons-arrow-right-20-solid"
/> --> />
<div class="flex items-center justify-end lg:flex-1"> <div class="flex items-center justify-end lg:flex-1">
<button <button

View File

@@ -10,34 +10,12 @@
}" }"
> >
<template #left> <template #left>
<NuxtLink to="/" class="flex items-end gap-2 text-xl text-gray-900 dark:text-white min-w-0 shrink-0" aria-label="Nuxt UI"> <NuxtLink to="/" class="flex items-end gap-2 font-bold text-xl text-gray-900 dark:text-white min-w-0" aria-label="Nuxt UI">
<LogoPro v-if="$route.path.startsWith('/pro')" class="w-auto h-6 shrink-0" /> <LogoPro v-if="$route.path.startsWith('/pro')" class="w-auto h-6 shrink-0" />
<Logo v-else class="w-auto h-6 shrink-0" /> <Logo v-else class="w-auto h-6 shrink-0" />
</NuxtLink>
<UDropdown <UBadge :label="$route.path.startsWith('/pro') ? `v${pkg.version.split('-')[0]}` : `v${config.version}`" variant="subtle" size="xs" class="-mb-[2px] rounded font-semibold truncate hidden sm:inline-flex" />
:items="[[{ label: $route.path.startsWith('/pro') ? `v${pkg.version.split('-')[0]}` : `v${config.version}`, class: 'text-primary-500 dark:text-primary-400' }, { label: 'v3.x', to: 'https://ui.nuxt.com' }]]" </NuxtLink>
:popper="{ strategy: 'absolute', offsetDistance: 11, placement: 'bottom-start' }"
:ui="{
background: 'dark:bg-gray-900',
ring: 'dark:ring-gray-800',
width: 'w-auto',
item: {
padding: 'p-1',
size: 'text-xs',
active: 'dark:bg-gray-800/50'
}
}"
>
<UButton
:label="$route.path.startsWith('/pro') ? `v${pkg.version.split('-')[0]}` : `v${config.version}`"
trailing-icon="i-lucide-chevron-down"
variant="outline"
size="2xs"
truncate
class="-mb-[6px] font-semibold rounded-full truncate ring-primary-500/25 dark:ring-primary-400/25 bg-primary-500/10 dark:bg-primary-400/10 hover:bg-primary-500/15 dark:hover:bg-primary-400/15 transition-colors"
/>
</UDropdown>
</template> </template>
<template #right> <template #right>
@@ -47,10 +25,10 @@
<UContentSearchButton :label="null" /> <UContentSearchButton :label="null" />
</UTooltip> </UTooltip>
<UColorModeButton class="hidden lg:inline-flex" /> <UColorModeButton />
<UButton <UButton
to="https://github.com/nuxt/ui/tree/v2" to="https://github.com/nuxt/ui"
target="_blank" target="_blank"
icon="i-simple-icons-github" icon="i-simple-icons-github"
aria-label="GitHub" aria-label="GitHub"

View File

@@ -9,7 +9,7 @@ onMounted(() => {
if (carbonads.value) { if (carbonads.value) {
const script = document.createElement('script') const script = document.createElement('script')
script.setAttribute('type', 'text/javascript') script.setAttribute('type', 'text/javascript')
script.setAttribute('src', 'https://cdn.carbonads.com/carbon.js?serve=CW7IC53I&placement=ui2nuxtcom') script.setAttribute('src', 'https://cdn.carbonads.com/carbon.js?serve=CWYIVK3E&placement=uinuxtcom')
script.setAttribute('id', '_carbonads_js') script.setAttribute('id', '_carbonads_js')
carbonads.value.appendChild(script) carbonads.value.appendChild(script)
} }

View File

@@ -45,7 +45,7 @@ const ui = {
inactive: 'text-gray-400 dark:text-gray-500' inactive: 'text-gray-400 dark:text-gray-500'
}, },
avatar: { avatar: {
size: '2xs' size: '2xs' as const
} }
} }
} }

View File

@@ -1,6 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { sub, format, isSameDay } from 'date-fns' import { sub, format, isSameDay, type Duration } from 'date-fns'
import type { Duration } from 'date-fns'
const ranges = [ const ranges = [
{ label: 'Last 7 days', duration: { days: 7 } }, { label: 'Last 7 days', duration: { days: 7 } },

View File

@@ -1,6 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { object, string, nonempty } from 'superstruct' import { object, string, nonempty, type Infer } from 'superstruct'
import type { Infer } from 'superstruct'
import type { FormSubmitEvent } from '#ui/types' import type { FormSubmitEvent } from '#ui/types'
const schema = object({ const schema = object({

View File

@@ -21,7 +21,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">
<UFormGroup label="Email" name="email"> <UFormGroup label="Email" name="email">
<UInput v-model="state.email" /> <UInput v-model="state.email" />
</UFormGroup> </UFormGroup>

View File

@@ -1,6 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { object, string } from 'yup' import { object, string, type InferType } from 'yup'
import type { InferType } from 'yup'
import type { FormSubmitEvent } from '#ui/types' import type { FormSubmitEvent } from '#ui/types'
const schema = object({ const schema = object({

View File

@@ -1,15 +0,0 @@
<template>
<UInput
v-model="name"
:maxlength="maxLength"
>
<template #trailing>
<span class="text-xs text-gray-500 dark:text-gray-400">{{ name.length }}/{{ maxLength }}</span>
</template>
</UInput>
</template>
<script setup lang="ts">
const name = ref('')
const maxLength = 10
</script>

View File

@@ -0,0 +1,102 @@
<script setup lang="ts">
const options = ref([
{ id: 1, name: 'bug', color: 'd73a4a' },
{ id: 2, name: 'documentation', color: '0075ca' },
{ id: 3, name: 'duplicate', color: 'cfd3d7' },
{ id: 4, name: 'enhancement', color: 'a2eeef' },
{ id: 5, name: 'good first issue', color: '7057ff' },
{ id: 6, name: 'help wanted', color: '008672' },
{ id: 7, name: 'invalid', color: 'e4e669' },
{ id: 8, name: 'question', color: 'd876e3' },
{ id: 9, name: 'wontfix', color: 'ffffff' }
])
const selected = ref([])
const labels = computed({
get: () => selected.value,
set: async (labels) => {
const promises = labels.map(async (label) => {
if (label.id) {
return label
}
// In a real app, you would make an API call to create the label
const response = {
id: options.value.length + 1,
name: label.name,
color: generateColorFromString(label.name)
}
options.value.push(response)
return response
})
selected.value = await Promise.all(promises)
}
})
function hashCode(str) {
let hash = 0
for (let i = 0; i < str.length; i++) {
hash = str.charCodeAt(i) + ((hash << 5) - hash)
}
return hash
}
function intToRGB(i) {
const c = (i & 0x00FFFFFF)
.toString(16)
.toUpperCase()
return '00000'.substring(0, 6 - c.length) + c
}
function generateColorFromString(str) {
return intToRGB(hashCode(str))
}
</script>
<template>
<USelectMenu
v-model="labels"
by="id"
name="labels"
:options="options"
option-attribute="name"
clearable
multiple
searchable
creatable
>
<template #label>
<template v-if="labels.length">
<span class="flex items-center -space-x-1">
<span v-for="label of labels" :key="label.id" class="flex-shrink-0 w-2 h-2 mt-px rounded-full" :style="{ background: `#${label.color}` }" />
</span>
<span>{{ labels.length }} label{{ labels.length > 1 ? 's' : '' }}</span>
</template>
<template v-else>
<span class="text-gray-500 dark:text-gray-400 truncate">Select labels</span>
</template>
</template>
<template #option="{ option }">
<span
class="flex-shrink-0 w-2 h-2 mt-px rounded-full"
:style="{ background: `#${option.color}` }"
/>
<span class="truncate">{{ option.name }}</span>
</template>
<template #option-create="{ option }">
<span class="flex-shrink-0">New label:</span>
<span
class="flex-shrink-0 w-2 h-2 mt-px rounded-full -mx-1"
:style="{ background: `#${generateColorFromString(option.name)}` }"
/>
<span class="block truncate">{{ option.name }}</span>
</template>
</USelectMenu>
</template>

View File

@@ -0,0 +1,105 @@
<script setup lang="ts">
const options = ref([
{ id: 1, name: 'bug', color: 'd73a4a' },
{ id: 2, name: 'documentation', color: '0075ca' },
{ id: 3, name: 'duplicate', color: 'cfd3d7' },
{ id: 4, name: 'enhancement', color: 'a2eeef' },
{ id: 5, name: 'good first issue', color: '7057ff' },
{ id: 6, name: 'help wanted', color: '008672' },
{ id: 7, name: 'invalid', color: 'e4e669' },
{ id: 8, name: 'question', color: 'd876e3' },
{ id: 9, name: 'wontfix', color: 'ffffff' }
])
const selected = ref([])
const labels = computed({
get: () => selected.value,
set: async (labels) => {
const promises = labels.map(async (label) => {
if (label.id) {
return label
}
// In a real app, you would make an API call to create the label
const response = {
id: options.value.length + 1,
name: label.name,
color: generateColorFromString(label.name)
}
options.value.push(response)
return response
})
selected.value = await Promise.all(promises)
}
})
function hashCode(str) {
let hash = 0
for (let i = 0; i < str.length; i++) {
hash = str.charCodeAt(i) + ((hash << 5) - hash)
}
return hash
}
function intToRGB(i) {
const c = (i & 0x00FFFFFF)
.toString(16)
.toUpperCase()
return '00000'.substring(0, 6 - c.length) + c
}
function generateColorFromString(str) {
return intToRGB(hashCode(str))
}
</script>
<template>
<USelectMenu
v-model="labels"
by="id"
name="labels"
:options="options"
option-attribute="name"
clearable
multiple
searchable
creatable
>
<template #label>
<template v-if="labels.length">
<span class="flex items-center -space-x-1">
<span v-for="label of labels" :key="label.id" class="flex-shrink-0 w-2 h-2 mt-px rounded-full" :style="{ background: `#${label.color}` }" />
</span>
<span>{{ labels.length }} label{{ labels.length > 1 ? 's' : '' }}</span>
</template>
<template v-else>
<span class="text-gray-500 dark:text-gray-400 truncate">Select labels</span>
</template>
</template>
<template #option="{ option }">
<span
class="flex-shrink-0 w-2 h-2 mt-px rounded-full"
:style="{ background: `#${option.color}` }"
/>
<span class="truncate">{{ option.name }}</span>
</template>
<template #option-create="{ option }">
<span class="flex-shrink-0">New label:</span>
<span
class="flex-shrink-0 w-2 h-2 mt-px rounded-full -mx-1"
:style="{ background: `#${generateColorFromString(option.name)}` }"
/>
<span class="block truncate">{{ option.name }}</span>
</template>
<template #clearable="{ onClear }">
<UButton icon="i-heroicons-trash-20-solid" size="xs" class="text-gray-400 dark:text-gray-500" variant="ghost" @click.capture.stop="onClear" />
</template>
</USelectMenu>
</template>

View File

@@ -31,7 +31,7 @@ onMounted(() => {
</script> </script>
<template> <template>
<ULandingGrid ref="section" class="lg:grid-cols-10 lg:gap-8 overflow-hidden p-1"> <ULandingGrid ref="section" class="lg:grid-cols-10 lg:gap-8 overflow-hidden p-px">
<div :ref="(el) => (refs[1] = el)" class="col-span-8 flex items-center animate-top"> <div :ref="(el) => (refs[1] = el)" class="col-span-8 flex items-center animate-top">
<RangeExample /> <RangeExample />
</div> </div>

View File

@@ -7,29 +7,13 @@ description: 'Learn how to install and configure Nuxt UI in your application.'
### Add to a Nuxt project ### Add to a Nuxt project
1. Install the `@nuxt/ui` dependency in your project: 1. Add `@nuxt/ui` module to your project:
::code-group ```bash
npx nuxi@latest module add ui
```bash [pnpm]
pnpm add @nuxt/ui@2
``` ```
```bash [yarn] 2. Add it to the `modules` section in your `nuxt.config.ts`:
yarn add @nuxt/ui@2
```
```bash [npm]
npm install @nuxt/ui@2
```
```bash [bun]
bun add @nuxt/ui@2
```
::
2. Register the `@nuxt/ui` module in your `nuxt.config.ts`:
```ts [nuxt.config.ts] ```ts [nuxt.config.ts]
export default defineNuxtConfig({ export default defineNuxtConfig({
@@ -45,7 +29,7 @@ That's it! You can now use all the components and composables in your Nuxt app
The Nuxt Starter template is available from the `nuxi init` command. The Nuxt Starter template is available from the `nuxi init` command.
```bash ```bash
npx nuxi@latest init -t ui2 npx nuxi@latest init -t ui
``` ```
Please check [nuxt/starter](https://github.com/nuxt/starter/tree/ui) for details. Please check [nuxt/starter](https://github.com/nuxt/starter/tree/ui) for details.
@@ -110,7 +94,7 @@ You can read more about this in the [Theming](/getting-started/theming#dark-mode
## TypeScript ## TypeScript
This module is written in TypeScript and provides typings for all the components and composables. You can look at the [source code](https://github.com/nuxt/ui/tree/v2/src/runtime/types) to see all the available types. This module is written in TypeScript and provides typings for all the components and composables. You can look at the [source code](https://github.com/nuxt/ui/tree/dev/src/runtime/types) to see all the available types.
::callout{icon="i-heroicons-light-bulb" to="https://nuxt.com/docs/guide/concepts/typescript" target="_blank"} ::callout{icon="i-heroicons-light-bulb" to="https://nuxt.com/docs/guide/concepts/typescript" target="_blank"}
You can read more about TypeScript on the official Nuxt documentation. You can read more about TypeScript on the official Nuxt documentation.
@@ -246,7 +230,7 @@ You can also add the following to your `.vscode/settings.json` to enable Intelli
| `prefix` | `u` | Define the prefix of the imported components. | | `prefix` | `u` | Define the prefix of the imported components. |
| `global` | `false` | Expose components globally. | | `global` | `false` | Expose components globally. |
| `safelistColors` | `['primary']` | Force safelisting of colors to need be purged. | | `safelistColors` | `['primary']` | Force safelisting of colors to need be purged. |
| `disableGlobalStyles` | `false` | Disable [global CSS styles](https://github.com/nuxt/ui/blob/v2/src/runtime/ui.css) injected by the module. | | `disableGlobalStyles` | `false` | Disable [global CSS styles](https://github.com/nuxt/ui/blob/dev/src/runtime/ui.css) injected by the module. |
Configure options in your `nuxt.config.ts` as such: Configure options in your `nuxt.config.ts` as such:
@@ -259,21 +243,19 @@ export default defineNuxtConfig({
}) })
``` ```
## Continuous Releases ## Edge
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. To use the latest updates pushed on the [`dev`](https://github.com/nuxt/ui/tree/dev) branch, you can use `@nuxt/ui-edge`.
Preview releases are automatically generated for every commit to the `dev` branch and pull requests targeting the `dev` branch. To use it into your project, replace the version in your `package.json` with the commit hash or pull request number. Update your `package.json` to the following:
```diff [package.json] ```diff [package.json]
{ {
"dependencies": { "devDependencies": {
- "@nuxt/ui": "^2.21.0", - "@nuxt/ui": "^2.11.0"
+ "@nuxt/ui": "https://pkg.pr.new/@nuxt/ui@bf1c9e7", + "@nuxt/ui": "npm:@nuxt/ui-edge@latest"
} }
} }
``` ```
::note Then run `pnpm install`, `yarn install` or `npm install`.
**pkg.pr.new** will automatically comment on PRs with the installation URL, making it easy to test changes.
::

View File

@@ -25,7 +25,7 @@ Try to change the `primary` and `gray` colors by clicking on the :u-icon{name="i
As this module uses Tailwind CSS under the hood, you can use any of the [Tailwind CSS colors](https://tailwindcss.com/docs/customizing-colors#color-palette-reference) or your own custom colors or groups, such as `brand.primary`. By default, the `primary` color is `green` and the `gray` color is `cool`. As this module uses Tailwind CSS under the hood, you can use any of the [Tailwind CSS colors](https://tailwindcss.com/docs/customizing-colors#color-palette-reference) or your own custom colors or groups, such as `brand.primary`. By default, the `primary` color is `green` and the `gray` color is `cool`.
When [using custom colors](https://tailwindcss.com/docs/customizing-colors#using-custom-colors) or [adding additional colors](https://tailwindcss.com/docs/customizing-colors#adding-additional-colors) through the `extend` key in your `tailwind.config.ts`, you'll need to make sure to define all the shades from `50` to `950` as most of them are used in the components config defined in [`ui.config/`](https://github.com/nuxt/ui/tree/v2/src/runtime/ui.config) directory. You can [generate your colors](https://tailwindcss.com/docs/customizing-colors#generating-colors) using tools such as https://uicolors.app/ for example. When [using custom colors](https://tailwindcss.com/docs/customizing-colors#using-custom-colors) or [adding additional colors](https://tailwindcss.com/docs/customizing-colors#adding-additional-colors) through the `extend` key in your `tailwind.config.ts`, you'll need to make sure to define all the shades from `50` to `950` as most of them are used in the components config defined in [`ui.config/`](https://github.com/nuxt/ui/tree/dev/src/runtime/ui.config) directory. You can [generate your colors](https://tailwindcss.com/docs/customizing-colors#generating-colors) using tools such as https://uicolors.app/ for example.
```ts [tailwind.config.ts] ```ts [tailwind.config.ts]
import type { Config } from 'tailwindcss' import type { Config } from 'tailwindcss'
@@ -118,7 +118,7 @@ export default defineAppConfig({
}) })
``` ```
The available options for each component should auto-complete, and you can review the defaults for each component using your IDE's function such as `Cmd`+`Click` (these files can be found in [`src/runtime/ui.config/`](https://github.com/nuxt/ui/tree/v2/src/runtime/ui.config)). The available options for each component should auto-complete, and you can review the defaults for each component using your IDE's function such as `Cmd`+`Click` (these files can be found in [`src/runtime/ui.config/`](https://github.com/nuxt/ui/tree/dev/src/runtime/ui.config)).
Thanks to [tailwind-merge](https://github.com/dcastil/tailwind-merge), the `app.config.ts` is smartly merged with the default config. This means you don't have to rewrite everything. Thanks to [tailwind-merge](https://github.com/dcastil/tailwind-merge), the `app.config.ts` is smartly merged with the default config. This means you don't have to rewrite everything.
@@ -221,52 +221,6 @@ export default defineAppConfig({
}) })
``` ```
### Extend Tailwind Merge
Tailwind Merge is a library that allows you to efficiently merge Tailwind CSS classes. It is used by this module to merge the classes from the `ui` prop, the `class` attribute, and the default classes.
::callout{icon="i-heroicons-light-bulb" to="https://github.com/dcastil/tailwind-merge" target="_blank"}
Learn more about Tailwind Merge.
::
By default, Tailwind Merge doesn't handle custom Tailwind CSS configuration like custom colors, spacing, or other utilities you may have defined. You'll need to extend it to handle your custom configuration.
You can extend Tailwind Merge by using the `tailwindMerge` option in your `app.config.ts`:
::code-group
```ts [app.config.ts]
export default defineAppConfig({
ui: {
tailwindMerge: {
extend: {
theme: {
spacing: ['sm', 'md', 'lg', 'xl', '2xl']
}
}
}
}
})
```
```ts [tailwind.config.ts]
import type { Config } from 'tailwindcss'
export default <Partial<Config>>{
theme: {
extend: {
spacing: {
sm: '0.5rem',
md: '1rem',
lg: '1.5rem',
xl: '2rem',
'2xl': '2.5rem'
}
}
}
}
```
::
## Dark mode ## Dark mode
All the components are styled with dark mode in mind. All the components are styled with dark mode in mind.
@@ -389,12 +343,6 @@ export default defineAppConfig({
loadingIcon: 'i-octicon-sync-24' loadingIcon: 'i-octicon-sync-24'
} }
}, },
inputMenu: {
default: {
selectedIcon: 'i-octicon-check-24',
trailingIcon: 'i-octicon-chevron-down-24'
}
},
select: { select: {
default: { default: {
loadingIcon: 'i-octicon-sync-24', loadingIcon: 'i-octicon-sync-24',
@@ -430,9 +378,6 @@ export default defineAppConfig({
sortButton: { sortButton: {
icon: 'i-octicon-arrow-switch-24' icon: 'i-octicon-arrow-switch-24'
}, },
expandButton: {
icon: 'i-octicon-chevron-down-24'
},
loadingState: { loadingState: {
icon: 'i-octicon-sync-24' icon: 'i-octicon-sync-24'
}, },
@@ -466,21 +411,6 @@ export default defineAppConfig({
default: { default: {
divider: 'i-octicon-chevron-right-24' divider: 'i-octicon-chevron-right-24'
} }
},
carousel: {
default: {
prevButton: {
icon: 'i-octicon-chevron-left-24'
},
nextButton: {
icon: 'i-octicon-chevron-right-24'
}
}
},
toggle: {
default: {
loadingIcon: 'i-octicon-sync-24'
}
} }
} }
}) })

View File

@@ -49,22 +49,18 @@ defineShortcuts({
Shortcuts keys are written as the literal keyboard key value. Combinations are made with `_` separator. Chained shortcuts are made with `-` separator. Shortcuts keys are written as the literal keyboard key value. Combinations are made with `_` separator. Chained shortcuts are made with `-` separator.
Modifiers are also available: Modifiers are also available:
| Modifier | Description | - `meta`: acts as `Command` for MacOS and `Control` for others
|----------|-------------| - `ctrl`: acts as `Control`
| `meta` | Acts as `Command (⌘)` on macOS and `Control (Ctrl)` on Windows/Linux. | - `shift`: acts as `Shift` and is only necessary for alphabetic keys
| `ctrl` | Represents the `Control (Ctrl)` key across all operating systems. |
| `shift` | Represents the `Shift` key, only needed for alphabetic keys (e.g., `shift_e`). |
Examples of keys: Examples of keys:
| Shortcut Key | Action | - `escape`: will trigger by hitting `Esc`
|---------------|--------| - `meta_k`: will trigger by hitting `⌘` and `K` at the same time on MacOS, and `Ctrl` and `K` on Windows and Linux
| `escape` | Triggers when `Esc` is pressed | - `ctrl_k`: will trigger by hitting `Ctrl` and `K` at the same time on MacOS, Windows and Linux
| `meta_k` | `⌘ + K` on Mac, `Ctrl + K` on Windows/Linux | - `shift_e`: will trigger by hitting `Shift` and `E` at the same time on MacOS, Windows and Linux
| `ctrl_k` | Triggers `Ctrl + K` on all OS | - `?`: will trigger by hitting `?` on some keyboard layouts, or for example `Shift` and `/`, which results in `?` on US Mac keyboards
| `shift_e` | Triggers `Shift + E` on all OS | - `g-d`: will trigger by hitting `g` then `d` with a maximum delay of 800ms by default
| `?` | Triggers `?` (Shift + `/` on US Mac keyboards) | - `arrowleft`: will trigger by hitting `` (also: `arrowright`, `arrowup`, `arrowdown`)
| `g-d` | Triggers when `g` then `d` are pressed within 800ms |
| `arrowleft` | Triggers when `←` is pressed (also: `arrowright`, `arrowup`, `arrowdown`) |
::callout{icon="i-heroicons-light-bulb"} ::callout{icon="i-heroicons-light-bulb"}
For a complete list of available shortcut keys, refer to the [`KeyboardEvent`](https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values) API docs. Note the `KeyboardEvent.key` has to be written in lowercase. For a complete list of available shortcut keys, refer to the [`KeyboardEvent`](https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values) API docs. Note the `KeyboardEvent.key` has to be written in lowercase.

View File

@@ -6,7 +6,7 @@ links:
to: 'https://headlessui.com/v1/vue/disclosure' to: 'https://headlessui.com/v1/vue/disclosure'
- label: GitHub - label: GitHub
icon: i-simple-icons-github icon: i-simple-icons-github
to: https://github.com/nuxt/ui/blob/v2/src/runtime/components/elements/Accordion.vue to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/elements/Accordion.vue
--- ---
## Usage ## Usage

View File

@@ -3,7 +3,7 @@ description: Display an alert element to draw attention.
links: links:
- label: GitHub - label: GitHub
icon: i-simple-icons-github icon: i-simple-icons-github
to: https://github.com/nuxt/ui/blob/v2/src/runtime/components/elements/Alert.vue to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/elements/Alert.vue
--- ---
## Usage ## Usage

View File

@@ -3,7 +3,7 @@ description: Display an image that represents a resource or a group of resources
links: links:
- label: GitHub - label: GitHub
icon: i-simple-icons-github icon: i-simple-icons-github
to: https://github.com/nuxt/ui/blob/v2/src/runtime/components/elements/Avatar.vue to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/elements/Avatar.vue
--- ---
## Usage ## Usage

View File

@@ -3,7 +3,7 @@ description: Display a short text to represent a status or a category.
links: links:
- label: GitHub - label: GitHub
icon: i-simple-icons-github icon: i-simple-icons-github
to: https://github.com/nuxt/ui/blob/v2/src/runtime/components/elements/Badge.vue to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/elements/Badge.vue
--- ---
## Usage ## Usage
@@ -141,74 +141,6 @@ Badge
You can customize the whole [preset](#preset) by using the `ui` prop. You can customize the whole [preset](#preset) by using the `ui` prop.
:: ::
### Icon
Use any icon from [Iconify](https://icones.js.org) by setting the `icon` prop by using this pattern: `i-{collection_name}-{icon_name}`.
Use the `leading` and `trailing` props to set the icon position or the `leading-icon` and `trailing-icon` props to set a different icon for each position.
::component-card
---
props:
icon: 'i-heroicons-rocket-launch'
size: 'sm'
color: 'primary'
variant: 'solid'
label: Badge
trailing: false
options:
- name: variant
restriction: only
values:
- solid
excludedProps:
- icon
- label
---
::
## Slots
### `leading`
Use the `#leading` slot to set the content of the leading icon.
::component-card
---
slots:
leading: <UAvatar src="https://avatars.githubusercontent.com/u/739984?v=4" size="3xs" />
baseProps:
color: 'gray'
props:
label: Badge
color: 'gray'
excludedProps:
- color
---
#leading
:u-avatar{src="https://avatars.githubusercontent.com/u/739984?v=4" size="3xs"}
::
### `trailing`
Use the `#trailing` slot to set the content of the trailing icon.
::component-card
---
slots:
trailing: <UIcon name="i-heroicons-rocket-launch" class="w-4 h-4" />
props:
label: Badge
color: 'gray'
excludedProps:
- color
---
#trailing
:u-icon{name="i-heroicons-rocket-launch" class="w-4 h-4"}
::
## Props ## Props
:component-props :component-props

View File

@@ -4,7 +4,7 @@ description: A list of links that indicate the current page's location within a
links: links:
- label: GitHub - label: GitHub
icon: i-simple-icons-github icon: i-simple-icons-github
to: https://github.com/nuxt/ui/blob/v2/src/runtime/components/navigation/Breadcrumb.vue to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/navigation/Breadcrumb.vue
--- ---
## Usage ## Usage

View File

@@ -3,7 +3,7 @@ description: Create a button with icon or link capabilities.
links: links:
- label: GitHub - label: GitHub
icon: i-simple-icons-github icon: i-simple-icons-github
to: https://github.com/nuxt/ui/blob/v2/src/runtime/components/elements/Button.vue to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/elements/Button.vue
--- ---
## Usage ## Usage

View File

@@ -3,7 +3,7 @@ description: Display a card for content with a header, body and footer.
links: links:
- label: GitHub - label: GitHub
icon: i-simple-icons-github icon: i-simple-icons-github
to: https://github.com/nuxt/ui/blob/v2/src/runtime/components/layout/Card.vue to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/layout/Card.vue
--- ---
## Usage ## Usage

View File

@@ -3,7 +3,7 @@ description: Display images or content in a scrollable area.
links: links:
- label: GitHub - label: GitHub
icon: i-simple-icons-github icon: i-simple-icons-github
to: https://github.com/nuxt/ui/blob/v2/src/runtime/components/elements/Carousel.vue to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/elements/Carousel.vue
--- ---
## Usage ## Usage

View File

@@ -3,7 +3,7 @@ description: Display a checkbox field.
links: links:
- label: GitHub - label: GitHub
icon: i-simple-icons-github icon: i-simple-icons-github
to: https://github.com/nuxt/ui/blob/v2/src/runtime/components/forms/Checkbox.vue to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/forms/Checkbox.vue
--- ---
## Usage ## Usage

View File

@@ -3,7 +3,7 @@ description: Display a chip indicator on any component.
links: links:
- label: GitHub - label: GitHub
icon: i-simple-icons-github icon: i-simple-icons-github
to: https://github.com/nuxt/ui/blob/v2/src/runtime/components/elements/Chip.vue to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/elements/Chip.vue
--- ---
## Usage ## Usage

View File

@@ -7,7 +7,7 @@ links:
to: 'https://headlessui.com/v1/vue/combobox' to: 'https://headlessui.com/v1/vue/combobox'
- label: GitHub - label: GitHub
icon: i-simple-icons-github icon: i-simple-icons-github
to: https://github.com/nuxt/ui/blob/v2/src/runtime/components/navigation/CommandPalette.vue to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/navigation/CommandPalette.vue
--- ---
## Usage ## Usage
@@ -274,7 +274,7 @@ hiddenCode: true
--- ---
:: ::
::callout{icon="i-simple-icons-github" to="https://github.com/nuxt/ui/blob/v2/docs/components/content/examples/CommandPaletteExampleThemeAlgolia.vue#L23" target="_blank"} ::callout{icon="i-simple-icons-github" to="https://github.com/nuxt/ui/blob/dev/docs/components/content/examples/CommandPaletteExampleThemeAlgolia.vue#L23" target="_blank"}
Take a look at the component! Take a look at the component!
:: ::
@@ -290,6 +290,6 @@ hiddenCode: true
--- ---
:: ::
::callout{icon="i-simple-icons-github" to="https://github.com/nuxt/ui/blob/v2/docs/components/content/examples/CommandPaletteExampleThemeRaycast.vue#L30" target="_blank"} ::callout{icon="i-simple-icons-github" to="https://github.com/nuxt/ui/blob/dev/docs/components/content/examples/CommandPaletteExampleThemeRaycast.vue#L30" target="_blank"}
Take a look at the component! Take a look at the component!
:: ::

View File

@@ -3,7 +3,7 @@ description: A container lets you center and constrain the width of your content
links: links:
- label: GitHub - label: GitHub
icon: i-simple-icons-github icon: i-simple-icons-github
to: https://github.com/nuxt/ui/blob/v2/src/runtime/components/layout/Container.vue to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/layout/Container.vue
--- ---
## Usage ## Usage

View File

@@ -4,7 +4,7 @@ description: Display a menu that appears on right click.
links: links:
- label: GitHub - label: GitHub
icon: i-simple-icons-github icon: i-simple-icons-github
to: https://github.com/nuxt/ui/blob/v2/src/runtime/components/overlays/ContextMenu.vue to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/overlays/ContextMenu.vue
--- ---
## Usage ## Usage

View File

@@ -3,7 +3,7 @@ description: Display a separator between content.
links: links:
- label: GitHub - label: GitHub
icon: i-simple-icons-github icon: i-simple-icons-github
to: https://github.com/nuxt/ui/blob/v2/src/runtime/components/layout/Divider.vue to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/layout/Divider.vue
--- ---
## Usage ## Usage

View File

@@ -6,7 +6,7 @@ links:
to: https://headlessui.com/v1/vue/menu to: https://headlessui.com/v1/vue/menu
- label: GitHub - label: GitHub
icon: i-simple-icons-github icon: i-simple-icons-github
to: https://github.com/nuxt/ui/blob/v2/src/runtime/components/elements/Dropdown.vue to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/elements/Dropdown.vue
--- ---
## Usage ## Usage

View File

@@ -4,7 +4,7 @@ description: Display a label and additional informations around a form element.
links: links:
- label: GitHub - label: GitHub
icon: i-simple-icons-github icon: i-simple-icons-github
to: https://github.com/nuxt/ui/blob/v2/src/runtime/components/forms/FormGroup.vue to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/forms/FormGroup.vue
--- ---

View File

@@ -3,7 +3,7 @@ description: Collect and validate form data.
links: links:
- label: GitHub - label: GitHub
icon: i-simple-icons-github icon: i-simple-icons-github
to: https://github.com/nuxt/ui/blob/v2/src/runtime/components/forms/Form.vue to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/forms/Form.vue
--- ---
## Usage ## Usage
@@ -14,7 +14,7 @@ It works with the [FormGroup](/components/form-group) component to display error
The form component requires two props: The form component 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 [Yup](https://github.com/jquense/yup), [Zod](https://github.com/colinhacks/zod), [Joi](https://github.com/hapijs/joi), [Valibot](https://github.com/fabian-hiller/valibot) or [Superstruct](https://github.com/ianstormtaylor/superstruct).
::callout{icon="i-heroicons-light-bulb"} ::callout{icon="i-heroicons-light-bulb"}
Note that **no validation library is included** by default, so ensure you **install the one you need**. Note that **no validation library is included** by default, so ensure you **install the one you need**.
@@ -190,7 +190,7 @@ hiddenCode: true
--- ---
:: ::
::callout{icon="i-simple-icons-github" to="https://github.com/nuxt/ui/blob/v2/docs/components/content/examples/FormExampleElements.vue" target="_blank"} ::callout{icon="i-simple-icons-github" to="https://github.com/nuxt/ui/blob/dev/docs/components/content/examples/FormExampleElements.vue" target="_blank"}
Take a look at the component! Take a look at the component!
:: ::

View File

@@ -4,7 +4,7 @@ description: Display a list of horizontal links.
links: links:
- label: GitHub - label: GitHub
icon: i-simple-icons-github icon: i-simple-icons-github
to: https://github.com/nuxt/ui/blob/v2/src/runtime/components/navigation/HorizontalNavigation.vue to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/navigation/HorizontalNavigation.vue
--- ---
## Usage ## Usage

View File

@@ -7,7 +7,7 @@ links:
to: 'https://headlessui.com/v1/vue/combobox' to: 'https://headlessui.com/v1/vue/combobox'
- label: GitHub - label: GitHub
icon: i-simple-icons-github icon: i-simple-icons-github
to: https://github.com/nuxt/ui/blob/v2/src/runtime/components/forms/InputMenu.vue to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/forms/InputMenu.vue
--- ---
## Usage ## Usage

View File

@@ -3,7 +3,7 @@ description: Display an input field.
links: links:
- label: GitHub - label: GitHub
icon: i-simple-icons-github icon: i-simple-icons-github
to: https://github.com/nuxt/ui/blob/v2/src/runtime/components/forms/Input.vue to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/forms/Input.vue
--- ---
## Usage ## Usage
@@ -142,7 +142,7 @@ props:
### Loading ### Loading
Use the `loading` prop to show a loading icon in the Input. Use the `loading` prop to show a loading icon and disable the Input.
Use the `loading-icon` prop to set a different icon or change it globally in `ui.input.default.loadingIcon`. Defaults to `i-heroicons-arrow-path-20-solid`. Use the `loading-icon` prop to set a different icon or change it globally in `ui.input.default.loadingIcon`. Defaults to `i-heroicons-arrow-path-20-solid`.
@@ -173,13 +173,6 @@ baseProps:
--- ---
:: ::
### Limit
Use the `maxlength` prop to limit the length of the Input.
:component-example{component="input-example-max-length"}
## Slots ## Slots
### `leading` ### `leading`

View File

@@ -4,7 +4,7 @@ description: Display a keyboard key in a text block.
links: links:
- label: GitHub - label: GitHub
icon: i-simple-icons-github icon: i-simple-icons-github
to: https://github.com/nuxt/ui/blob/v2/src/runtime/components/elements/Kbd.vue to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/elements/Kbd.vue
navigation: navigation:
title: 'Kbd' title: 'Kbd'
--- ---

View File

@@ -4,7 +4,7 @@ description: Render a NuxtLink but with superpowers.
links: links:
- label: GitHub - label: GitHub
icon: i-simple-icons-github icon: i-simple-icons-github
to: https://github.com/nuxt/ui/blob/v2/src/runtime/components/elements/Link.vue to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/elements/Link.vue
--- ---
## Usage ## Usage
@@ -14,7 +14,6 @@ The Link component is a wrapper around [`<NuxtLink>`](https://nuxt.com/docs/api/
- `inactive-class` prop to set a class when the link is inactive, `active-class` is used when active. - `inactive-class` prop to set a class when the link is inactive, `active-class` is used when active.
- `exact` prop to style with `active-class` when the link is active and the route is exactly the same as the current route. - `exact` prop to style with `active-class` when the link is active and the route is exactly the same as the current route.
- `exact-query` and `exact-hash` props to style with `active-class` when the link is active and the query or hash is exactly the same as the current query or hash. - `exact-query` and `exact-hash` props to style with `active-class` when the link is active and the query or hash is exactly the same as the current query or hash.
- use `exact-query="partial"` to style with `active-class` when the link is active and the query partially match the current query.
The incentive behind this is to provide the same API as NuxtLink back in Nuxt 2 / Vue 2. You can read more about it in the Vue Router [migration from Vue 2](https://router.vuejs.org/guide/migration/#removal-of-the-exact-prop-in-router-link) guide. The incentive behind this is to provide the same API as NuxtLink back in Nuxt 2 / Vue 2. You can read more about it in the Vue Router [migration from Vue 2](https://router.vuejs.org/guide/migration/#removal-of-the-exact-prop-in-router-link) guide.

View File

@@ -4,7 +4,7 @@ description: Display a gauge meter that fills or depletes.
links: links:
- label: GitHub - label: GitHub
icon: i-simple-icons-github icon: i-simple-icons-github
to: https://github.com/nuxt/ui/blob/v2/src/runtime/components/elements/Meter.vue to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/elements/Meter.vue
--- ---
## Usage ## Usage

View File

@@ -6,7 +6,7 @@ links:
to: 'https://headlessui.com/v1/vue/dialog' to: 'https://headlessui.com/v1/vue/dialog'
- label: GitHub - label: GitHub
icon: i-simple-icons-github icon: i-simple-icons-github
to: https://github.com/nuxt/ui/blob/v2/src/runtime/components/overlays/Modal.vue to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/overlays/Modal.vue
--- ---
## Usage ## Usage

View File

@@ -3,7 +3,7 @@ description: Display a toast notification in your app.
links: links:
- label: GitHub - label: GitHub
icon: i-simple-icons-github icon: i-simple-icons-github
to: https://github.com/nuxt/ui/blob/v2/src/runtime/components/overlays/Notification.vue to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/overlays/Notification.vue
--- ---
## Usage ## Usage
@@ -137,9 +137,9 @@ excludedProps:
### Timeout ### Timeout
Use the `timeout` prop to configure how long the Notification will remain. The default value is `5000`, set it to `0` to disable the timeout. The `pauseTimeoutOnHover` prop (`true` by default) controls whether hovering the notification should pause the timeout. Use the `timeout` prop to configure how long the Notification will remain. The default value is `5000`, set it to `0` to disable the timeout.
You will see a progress bar at the bottom of the Notification which will indicate the remaining time. When hovering the Notification, the progress bar will be paused if `pauseTimeoutOnHover` is enabled; otherwise, it won't stop. You will see a progress bar at the bottom of the Notification which will indicate the remaining time. When hovering the Notification, the progress bar will be paused.
::component-card ::component-card
--- ---
@@ -149,7 +149,6 @@ baseProps:
description: 'This is a notification.' description: 'This is a notification.'
props: props:
timeout: 60000 timeout: 60000
pauseTimeoutOnHover: true
--- ---
:: ::

View File

@@ -3,7 +3,7 @@ description: Add a pagination to handle pages.
links: links:
- label: GitHub - label: GitHub
icon: i-simple-icons-github icon: i-simple-icons-github
to: https://github.com/nuxt/ui/blob/v2/src/runtime/components/navigation/Pagination.vue to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/navigation/Pagination.vue
--- ---
## Usage ## Usage

View File

@@ -6,7 +6,7 @@ links:
to: 'https://headlessui.com/v1/vue/popover' to: 'https://headlessui.com/v1/vue/popover'
- label: GitHub - label: GitHub
icon: i-simple-icons-github icon: i-simple-icons-github
to: https://github.com/nuxt/ui/blob/v2/src/runtime/components/overlays/Popover.vue to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/overlays/Popover.vue
--- ---
## Usage ## Usage

View File

@@ -4,7 +4,7 @@ description: Show a horizontal bar to indicate task progression.
links: links:
- label: GitHub - label: GitHub
icon: i-simple-icons-github icon: i-simple-icons-github
to: https://github.com/nuxt/ui/blob/v2/src/runtime/components/elements/Progress.vue to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/elements/Progress.vue
--- ---
## Usage ## Usage

View File

@@ -4,7 +4,7 @@ description: Display a set of radio buttons.
links: links:
- label: GitHub - label: GitHub
icon: i-simple-icons-github icon: i-simple-icons-github
to: https://github.com/nuxt/ui/blob/v2/src/runtime/components/forms/RadioGroup.vue to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/forms/RadioGroup.vue
--- ---
## Usage ## Usage

View File

@@ -3,7 +3,7 @@ description: Display a range field
links: links:
- label: GitHub - label: GitHub
icon: i-simple-icons-github icon: i-simple-icons-github
to: https://github.com/nuxt/ui/blob/v2/src/runtime/components/forms/Range.vue to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/forms/Range.vue
--- ---
## Usage ## Usage

View File

@@ -7,7 +7,7 @@ links:
to: 'https://headlessui.com/v1/vue/listbox' to: 'https://headlessui.com/v1/vue/listbox'
- label: GitHub - label: GitHub
icon: i-simple-icons-github icon: i-simple-icons-github
to: https://github.com/nuxt/ui/blob/v2/src/runtime/components/forms/SelectMenu.vue to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/forms/SelectMenu.vue
--- ---
## Usage ## Usage
@@ -156,7 +156,38 @@ Use the `searchableLazy` prop to control the immediacy of data requests.
--- ---
component: 'select-menu-example-search-async' component: 'select-menu-example-search-async'
componentProps: componentProps:
class: 'w-full lg:w-48' class: 'w-full lg:w-48'
---
::
## Clearable
The `clearable` prop allows users to easily remove their selected option(s) with a clear button.
::component-example
---
component: 'select-menu-example-clearable'
componentProps:
class: 'w-full lg:w-52'
---
::
### Customization
#### Slot Props
The slot provides four key props:
| Prop | Type | Description |
|------|------|-------------|
| `selected` | `Object` | The currently selected value/item in the component |
| `disabled` | `Boolean` | Whether the component is in a disabled state |
| `loading` | `Boolean` | Whether the component is in a loading state |
| `onClear` | `Function` | Callback function to clear the selected value when the clear button is clicked |
::component-example
---
component: 'select-menu-example-clearable-customization'
componentProps:
class: 'w-full lg:w-52'
--- ---
:: ::

View File

@@ -3,7 +3,7 @@ description: Display a select field.
links: links:
- label: GitHub - label: GitHub
icon: i-simple-icons-github icon: i-simple-icons-github
to: https://github.com/nuxt/ui/blob/v2/src/runtime/components/forms/Select.vue to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/forms/Select.vue
--- ---
## Usage ## Usage

View File

@@ -3,7 +3,7 @@ description: Display a placeholder while content is loading.
links: links:
- label: GitHub - label: GitHub
icon: i-simple-icons-github icon: i-simple-icons-github
to: https://github.com/nuxt/ui/blob/v2/src/runtime/components/layout/Skeleton.vue to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/layout/Skeleton.vue
--- ---
## Usage ## Usage

View File

@@ -6,7 +6,7 @@ links:
to: 'https://headlessui.com/v1/vue/dialog' to: 'https://headlessui.com/v1/vue/dialog'
- label: GitHub - label: GitHub
icon: i-simple-icons-github icon: i-simple-icons-github
to: https://github.com/nuxt/ui/blob/v2/src/runtime/components/overlays/Slideover.vue to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/overlays/Slideover.vue
--- ---
## Usage ## Usage

View File

@@ -3,7 +3,7 @@ description: 'Display data in a table.'
links: links:
- label: GitHub - label: GitHub
icon: i-simple-icons-github icon: i-simple-icons-github
to: https://github.com/nuxt/ui/blob/v2/src/runtime/components/data/Table.vue to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/data/Table.vue
--- ---
## Usage ## Usage
@@ -62,7 +62,7 @@ extraClass: 'overflow-hidden'
padding: false padding: false
component: 'table-example-columns-selectable' component: 'table-example-columns-selectable'
componentProps: componentProps:
class: 'flex-1 flex-col overflow-hidden min-h-[230px]' class: 'flex-1 flex-col overflow-hidden'
--- ---
:: ::
@@ -346,7 +346,7 @@ componentProps:
### Contextmenu ### Contextmenu
Use the `contextmenu` listener on your Table to make the rows right-clickable. The function will receive the original event as the first argument and the row as the second argument. Use the `contextmenu` listener on your Table to make the rows righ-clickable. The function will receive the original event as the first argument and the row as the second argument.
You can use this to open a [ContextMenu](/components/context-menu) for that row. You can use this to open a [ContextMenu](/components/context-menu) for that row.
@@ -769,6 +769,6 @@ hiddenCode: true
--- ---
:: ::
::callout{icon="i-simple-icons-github" to="https://github.com/nuxt/ui/blob/v2/docs/components/content/examples/TableExampleAdvanced.vue" target="_blank"} ::callout{icon="i-simple-icons-github" to="https://github.com/nuxt/ui/blob/dev/docs/components/content/examples/TableExampleAdvanced.vue" target="_blank"}
Take a look at the component! Take a look at the component!
:: ::

View File

@@ -3,7 +3,7 @@ description: A set of tab panels that are displayed one at a time.
links: links:
- label: GitHub - label: GitHub
icon: i-simple-icons-github icon: i-simple-icons-github
to: https://github.com/nuxt/ui/blob/v2/src/runtime/components/navigation/Tabs.vue to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/navigation/Tabs.vue
--- ---
## Usage ## Usage

View File

@@ -3,7 +3,7 @@ description: Display a textarea field.
links: links:
- label: GitHub - label: GitHub
icon: i-simple-icons-github icon: i-simple-icons-github
to: https://github.com/nuxt/ui/blob/v2/src/runtime/components/forms/Textarea.vue to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/forms/Textarea.vue
--- ---
## Usage ## Usage

View File

@@ -6,7 +6,7 @@ links:
to: 'https://headlessui.com/v1/vue/switch' to: 'https://headlessui.com/v1/vue/switch'
- label: GitHub - label: GitHub
icon: i-simple-icons-github icon: i-simple-icons-github
to: https://github.com/nuxt/ui/blob/v2/src/runtime/components/forms/Toggle.vue to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/forms/Toggle.vue
--- ---
## Usage ## Usage

View File

@@ -3,7 +3,7 @@ description: Display content that appears on hover next to an element.
links: links:
- label: GitHub - label: GitHub
icon: i-simple-icons-github icon: i-simple-icons-github
to: https://github.com/nuxt/ui/blob/v2/src/runtime/components/overlays/Tooltip.vue to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/overlays/Tooltip.vue
--- ---
## Usage ## Usage

View File

@@ -4,7 +4,7 @@ description: Display a list of vertical links.
links: links:
- label: GitHub - label: GitHub
icon: i-simple-icons-github icon: i-simple-icons-github
to: https://github.com/nuxt/ui/blob/v2/src/runtime/components/navigation/VerticalNavigation.vue to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/navigation/VerticalNavigation.vue
--- ---
## Usage ## Usage

View File

@@ -3,7 +3,7 @@ title: '<span class="text-primary">Nuxt UI</span> Releases'
head.title: Releases head.title: Releases
description: Follow the latest releases and updates happening on the nuxt/ui repository. description: Follow the latest releases and updates happening on the nuxt/ui repository.
links: links:
- label: Get started - label: Get Started
trailingIcon: i-heroicons-arrow-right-20-solid trailingIcon: i-heroicons-arrow-right-20-solid
to: /getting-started to: /getting-started
size: md size: md

View File

@@ -3,8 +3,6 @@
<div> <div>
<NuxtLoadingIndicator /> <NuxtLoadingIndicator />
<Banner />
<Header :links="links" /> <Header :links="links" />
<UContainer> <UContainer>

View File

@@ -15,7 +15,7 @@ export default defineNuxtConfig({
] ]
: [ : [
'@nuxt/ui-pro', '@nuxt/ui-pro',
process.env.NUXT_GITHUB_TOKEN && ['github:nuxt/ui-pro/.docs#v1', { giget: { auth: process.env.NUXT_GITHUB_TOKEN } }] process.env.NUXT_GITHUB_TOKEN && ['github:nuxt/ui-pro/.docs#dev', { giget: { auth: process.env.NUXT_GITHUB_TOKEN } }]
].filter(Boolean), ].filter(Boolean),
modules: [ modules: [
@@ -26,11 +26,13 @@ export default defineNuxtConfig({
module, module,
'@nuxtjs/plausible', '@nuxtjs/plausible',
'@vueuse/nuxt', '@vueuse/nuxt',
'nuxt-component-meta' 'nuxt-component-meta',
'nuxt-cloudflare-analytics',
'modules/content-examples-code'
], ],
site: { site: {
url: 'https://ui2.nuxt.com' url: 'https://ui.nuxt.com'
}, },
content: { content: {
@@ -52,7 +54,7 @@ export default defineNuxtConfig({
prefix: '/pro', prefix: '/pro',
driver: 'github', driver: 'github',
repo: 'nuxt/ui-pro', repo: 'nuxt/ui-pro',
branch: 'v1', branch: 'dev',
dir: '.docs/content/pro', dir: '.docs/content/pro',
token: process.env.NUXT_GITHUB_TOKEN || '' token: process.env.NUXT_GITHUB_TOKEN || ''
} }
@@ -93,11 +95,6 @@ export default defineNuxtConfig({
vite: { vite: {
optimizeDeps: { optimizeDeps: {
include: ['date-fns'] include: ['date-fns']
},
server: {
fs: {
allow: process.env.NUXT_UI_PRO_PATH ? [resolve(process.env.NUXT_UI_PRO_PATH)] : undefined
}
} }
}, },
@@ -118,6 +115,11 @@ export default defineNuxtConfig({
} }
}, },
cloudflareAnalytics: {
token: '1e2b0c5e9a214f0390b9b94e043d8d4c',
scriptPath: false
},
componentMeta: { componentMeta: {
exclude: [ exclude: [
'@nuxt/content', '@nuxt/content',

View File

@@ -3,28 +3,28 @@
"private": true, "private": true,
"type": "module", "type": "module",
"dependencies": { "dependencies": {
"@iconify-json/heroicons": "^1.2.2", "@iconify-json/heroicons": "^1.2.1",
"@iconify-json/lucide": "^1.2.57", "@iconify-json/simple-icons": "^1.2.11",
"@iconify-json/simple-icons": "^1.2.44", "@iconify-json/vscode-icons": "^1.2.2",
"@iconify-json/vscode-icons": "^1.2.23",
"@nuxt/content": "^2.13.4", "@nuxt/content": "^2.13.4",
"@nuxt/fonts": "^0.11.4", "@nuxt/fonts": "^0.10.2",
"@nuxt/image": "^1.10.0", "@nuxt/image": "^1.8.1",
"@nuxt/ui": "latest", "@nuxt/ui": "latest",
"@nuxt/ui-pro": "https://pkg.pr.new/@nuxt/ui-pro@fdb0248", "@nuxt/ui-pro": "^1.5.0",
"@nuxtjs/plausible": "^1.2.0", "@nuxtjs/plausible": "^1.0.3",
"@octokit/rest": "^21.1.1", "@octokit/rest": "^21.0.2",
"@vueuse/nuxt": "^13.5.0", "@vueuse/nuxt": "^11.2.0",
"date-fns": "^4.1.0", "date-fns": "^4.1.0",
"joi": "^17.13.3", "joi": "^17.13.3",
"nuxt": "^4.0.1", "nuxt": "^3.14.159",
"nuxt-component-meta": "^0.12.1", "nuxt-cloudflare-analytics": "^1.0.8",
"nuxt-og-image": "^5.1.9", "nuxt-component-meta": "^0.9.0",
"prettier": "^3.6.2", "nuxt-og-image": "^3.0.8",
"ufo": "^1.6.1", "prettier": "^3.3.3",
"ufo": "^1.5.4",
"v-calendar": "^3.1.2", "v-calendar": "^3.1.2",
"valibot": "^1.1.0", "valibot": "^0.42.1",
"yup": "^1.6.1", "yup": "^1.4.0",
"zod": "^4.0.5" "zod": "^3.23.8"
} }
} }

View File

@@ -78,7 +78,7 @@ defineOgImageComponent('Docs', {
const communityLinks = computed(() => [{ const communityLinks = computed(() => [{
icon: 'i-heroicons-pencil-square', icon: 'i-heroicons-pencil-square',
label: 'Edit this page', label: 'Edit this page',
to: `https://github.com/nuxt/ui/edit/v2/docs/content/${page?.value?._file}`, to: `https://github.com/nuxt/ui/edit/dev/docs/content/${page?.value?._file}`,
target: '_blank' target: '_blank'
}, { }, {
icon: 'i-heroicons-star', icon: 'i-heroicons-star',

View File

@@ -23,10 +23,9 @@
</template> </template>
<template #links> <template #links>
<UButton label="Get started" size="lg" to="/getting-started/installation" /> <UButton label="Get Started" trailing-icon="i-heroicons-arrow-right-20-solid" size="lg" to="/getting-started/installation" />
<UButton label="Explore components" trailing-icon="i-heroicons-arrow-right-20-solid" color="gray" size="lg" to="/components/button" />
<!-- <UInput <UInput
v-model="source" v-model="source"
color="gray" color="gray"
readonly readonly
@@ -48,7 +47,7 @@
@click="copy(source)" @click="copy(source)"
/> />
</template> </template>
</UInput> --> </UInput>
</template> </template>
<ClientOnly> <ClientOnly>
@@ -193,16 +192,16 @@
</div> </div>
</ULandingHero> </ULandingHero>
<!-- <ULandingSection v-for="(section, index) in page.pro.sections" :key="index" v-bind="section" class="!pt-0"> <ULandingSection v-for="(section, index) in page.pro.sections" :key="index" v-bind="section" class="!pt-0">
<MDC <MDC
v-if="section.code" v-if="section.code"
:value="section.code" :value="section.code"
tag="pre" tag="pre"
class="prose prose-primary dark:prose-invert max-w-none" class="prose prose-primary dark:prose-invert max-w-none"
/> />
</ULandingSection> --> </ULandingSection>
<!-- <div ref="sectionRef" :style="{ '--y': `${y}px`, '--inc': `${inc}px` }" class="_screen_xl"> <div ref="sectionRef" :style="{ '--y': `${y}px`, '--inc': `${inc}px` }" class="_screen_xl">
<ULandingSection class="sticky h-screen top-0 flex !pb-16" :ui="{ container: 'flex-1 sm:gap-y-12' }"> <ULandingSection class="sticky h-screen top-0 flex !pb-16" :ui="{ container: 'flex-1 sm:gap-y-12' }">
<template #title> <template #title>
<Transition name="fade" mode="out-in"> <Transition name="fade" mode="out-in">
@@ -282,6 +281,8 @@
<template #page-body> <template #page-body>
<div class="-mt-8 prose prose-primary prose-sm dark:prose-invert max-w-none"> <div class="-mt-8 prose prose-primary prose-sm dark:prose-invert max-w-none">
<MDC :value="md" tag="div" /> <MDC :value="md" tag="div" />
<!-- <hr class="border-gray-800/10 dark:border-gray-200/10 !mt-5"> -->
</div> </div>
</template> </template>
@@ -324,7 +325,7 @@
</template> </template>
<template #links> <template #links>
<UButton label="Get started" icon="i-heroicons-rocket-launch" size="md" /> <UButton label="Get Started" icon="i-heroicons-rocket-launch" size="md" />
<UInput <UInput
model-value="npm i @nuxt/ui" model-value="npm i @nuxt/ui"
@@ -373,9 +374,9 @@
</ULandingSection> </ULandingSection>
<div class="h-[calc(var(--inc)*42)]" /> <div class="h-[calc(var(--inc)*42)]" />
</div> --> </div>
<!-- <div class="_not_screen_xl"> <div class="_not_screen_xl">
<ULandingSection> <ULandingSection>
<template #title> <template #title>
<span v-html="page.pro.landing?.title" /> <span v-html="page.pro.landing?.title" />
@@ -402,15 +403,15 @@
<source src="https://res.cloudinary.com/nuxt/video/upload/v1698923398/ui-pro/nuxt-ui-pro-docs-demo_jm6ubr.ogg" type="video/ogg"> <source src="https://res.cloudinary.com/nuxt/video/upload/v1698923398/ui-pro/nuxt-ui-pro-docs-demo_jm6ubr.ogg" type="video/ogg">
</video> </video>
</ULandingSection> </ULandingSection>
</div> --> </div>
</template> </template>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import type { NavItem } from '@nuxt/content' import type { ParsedContent, NavItem } from '@nuxt/content'
import { useElementBounding, useWindowScroll, breakpointsTailwind, useBreakpoints } from '@vueuse/core' import { useElementBounding, useWindowScroll, useElementSize, breakpointsTailwind, useBreakpoints } from '@vueuse/core'
// import type { HomeProBlock } from '~/types' import type { HomeProBlock } from '~/types'
const { data: page } = await useAsyncData('index', () => queryContent('/').findOne()) const { data: page } = await useAsyncData('index', () => queryContent('/').findOne())
const { data: module } = await useFetch<{ const { data: module } = await useFetch<{
@@ -422,7 +423,6 @@ const { data: module } = await useFetch<{
username: string username: string
}[] }[]
}>('https://api.nuxt.com/modules/ui', { }>('https://api.nuxt.com/modules/ui', {
key: 'stats',
transform: ({ stats, contributors }) => ({ stats, contributors }) transform: ({ stats, contributors }) => ({ stats, contributors })
}) })
@@ -434,321 +434,321 @@ useSeoMeta({
ogTitle: page.value.title, ogTitle: page.value.title,
description: page.value.description, description: page.value.description,
ogDescription: page.value.description, ogDescription: page.value.description,
ogImage: 'https://ui2.nuxt.com/social-card.png', ogImage: 'https://ui.nuxt.com/social-card.png',
twitterImage: 'https://ui2.nuxt.com/social-card.png' twitterImage: 'https://ui.nuxt.com/social-card.png'
}) })
// const source = ref('npx nuxi@latest module add ui') const source = ref('npx nuxi@latest module add ui')
const sectionRef = ref() const sectionRef = ref()
// const demoRef = ref(null) const demoRef = ref(null)
const start = ref(0) const start = ref(0)
// const { $ui } = useNuxtApp() const { $ui } = useNuxtApp()
// const { height } = useElementSize(demoRef) const { height } = useElementSize(demoRef)
const { top } = useElementBounding(sectionRef) const { top } = useElementBounding(sectionRef)
const { y } = useWindowScroll() const { y } = useWindowScroll()
const config = useRuntimeConfig().public const config = useRuntimeConfig().public
// const { copy, copied } = useClipboard({ source }) const { copy, copied } = useClipboard({ source })
const breakpoints = useBreakpoints(breakpointsTailwind) const breakpoints = useBreakpoints(breakpointsTailwind)
const lgAndLarger = breakpoints.greaterOrEqual('lg') const lgAndLarger = breakpoints.greaterOrEqual('lg')
const { format } = Intl.NumberFormat('en', { notation: 'compact' }) const { format } = Intl.NumberFormat('en', { notation: 'compact' })
// const steps = { const steps = {
// header: 0, header: 0,
// footer: 5, footer: 5,
// landing: 10, landing: 10,
// docs: 27 docs: 27
// } }
// Computed // Computed
// const inc = computed(() => (height.value - 32 - 64 - 32 - 32) / 4) const inc = computed(() => (height.value - 32 - 64 - 32 - 32) / 4)
// const landingBlocks = computed(() => isAfterStep(steps.landing) && isBeforeStep(steps.docs) const landingBlocks = computed(() => isAfterStep(steps.landing) && isBeforeStep(steps.docs)
// ? [{ ? [{
// class: 'inset-x-0 top-20 bottom-20 overflow-hidden', class: 'inset-x-0 top-20 bottom-20 overflow-hidden',
// inactive: true, inactive: true,
// children: [{ children: [{
// name: 'ULandingHero', name: 'ULandingHero',
// to: '/pro/components/landing-hero', to: '/pro/components/landing-hero',
// class: [ class: [
// 'inset-4', 'inset-4',
// isAfterStep(steps.landing + 2) && '-top-[calc(var(--y)-var(--step-y)-1rem)] bottom-[calc(var(--y)-var(--step-y)+1rem)]' isAfterStep(steps.landing + 2) && '-top-[calc(var(--y)-var(--step-y)-1rem)] bottom-[calc(var(--y)-var(--step-y)+1rem)]'
// ].filter(Boolean).join(' '), ].filter(Boolean).join(' '),
// style: { style: {
// '--step-y': `${getStepY(steps.landing + 2)}px` '--step-y': `${getStepY(steps.landing + 2)}px`
// }, },
// inactive: isAfterStep(steps.landing + 1), inactive: isAfterStep(steps.landing + 1),
// children: [{ children: [{
// slot: 'landing-hero', slot: 'landing-hero',
// class: 'inset-4' class: 'inset-4'
// }] }]
// }, isAfterStep(steps.landing + 2) && { }, isAfterStep(steps.landing + 2) && {
// name: 'ULandingSection', name: 'ULandingSection',
// to: '/pro/components/landing-section', to: '/pro/components/landing-section',
// class: [ class: [
// 'inset-4', 'inset-4',
// isBeforeStep(steps.landing + 6) && '-top-[calc(var(--y)-var(--prev-step-y)-var(--height)-1rem)] bottom-[calc(var(--y)-var(--prev-step-y)-var(--height)+1rem)]', isBeforeStep(steps.landing + 6) && '-top-[calc(var(--y)-var(--prev-step-y)-var(--height)-1rem)] bottom-[calc(var(--y)-var(--prev-step-y)-var(--height)+1rem)]',
// isAfterStep(steps.landing + 10) && '-top-[calc(var(--y)-var(--step-y)-1rem)] bottom-[calc(var(--y)-var(--step-y)+1rem)]' isAfterStep(steps.landing + 10) && '-top-[calc(var(--y)-var(--step-y)-1rem)] bottom-[calc(var(--y)-var(--step-y)+1rem)]'
// ].filter(Boolean).join(' '), ].filter(Boolean).join(' '),
// style: { style: {
// '--height': (inc.value * 4) + 'px', '--height': (inc.value * 4) + 'px',
// '--step-y': `${getStepY(steps.landing + 10)}px`, '--step-y': `${getStepY(steps.landing + 10)}px`,
// '--prev-step-y': `${getStepY(steps.landing + 2)}px` '--prev-step-y': `${getStepY(steps.landing + 2)}px`
// }, },
// inactive: isAfterStep(steps.landing + 7), inactive: isAfterStep(steps.landing + 7),
// children: [{ children: [{
// slot: 'landing-section', slot: 'landing-section',
// class: 'inset-x-4 top-16' class: 'inset-x-4 top-16'
// }, { }, {
// name: 'ULandingGrid', name: 'ULandingGrid',
// to: '/pro/components/landing-grid', to: '/pro/components/landing-grid',
// class: ['inset-x-4 bottom-4 top-48', isAfterStep(steps.landing + 8) && 'grid grid-cols-4 gap-4 p-4'].filter(Boolean).join(' '), class: ['inset-x-4 bottom-4 top-48', isAfterStep(steps.landing + 8) && 'grid grid-cols-4 gap-4 p-4'].filter(Boolean).join(' '),
// inactive: isAfterStep(steps.landing + 8), inactive: isAfterStep(steps.landing + 8),
// children: [isAfterStep(steps.landing + 9) children: [isAfterStep(steps.landing + 9)
// ? { ? {
// slot: 'landing-card-1', slot: 'landing-card-1',
// class: '!relative' class: '!relative'
// } }
// : { : {
// name: 'ULandingCard', name: 'ULandingCard',
// to: '/pro/components/landing-card', to: '/pro/components/landing-card',
// class: '!relative h-full', class: '!relative h-full',
// inactive: false inactive: false
// }, isAfterStep(steps.landing + 9) }, isAfterStep(steps.landing + 9)
// ? { ? {
// slot: 'landing-card-2', slot: 'landing-card-2',
// class: '!relative h-full' class: '!relative h-full'
// } }
// : { : {
// name: 'ULandingCard', name: 'ULandingCard',
// to: '/pro/components/landing-card', to: '/pro/components/landing-card',
// class: '!relative h-full', class: '!relative h-full',
// inactive: false inactive: false
// }, isAfterStep(steps.landing + 9) }, isAfterStep(steps.landing + 9)
// ? { ? {
// slot: 'landing-card-3', slot: 'landing-card-3',
// class: '!relative h-full' class: '!relative h-full'
// } }
// : { : {
// name: 'ULandingCard', name: 'ULandingCard',
// to: '/pro/components/landing-card', to: '/pro/components/landing-card',
// class: '!relative h-full', class: '!relative h-full',
// inactive: false inactive: false
// }, isAfterStep(steps.landing + 9) }, isAfterStep(steps.landing + 9)
// ? { ? {
// slot: 'landing-card-4', slot: 'landing-card-4',
// class: '!relative h-full' class: '!relative h-full'
// } }
// : { : {
// name: 'ULandingCard', name: 'ULandingCard',
// to: '/pro/components/landing-card', to: '/pro/components/landing-card',
// class: '!relative h-full', class: '!relative h-full',
// inactive: false inactive: false
// }] }]
// }] }]
// }, isAfterStep(steps.landing + 10) && { }, isAfterStep(steps.landing + 10) && {
// name: 'ULandingSection', name: 'ULandingSection',
// to: '/pro/components/landing-section', to: '/pro/components/landing-section',
// class: [ class: [
// 'inset-4', 'inset-4',
// isBeforeStep(steps.landing + 14) && '-top-[calc(var(--y)-var(--prev-step-y)-var(--height)-1rem)] bottom-[calc(var(--y)-var(--prev-step-y)-var(--height)+1rem)]' isBeforeStep(steps.landing + 14) && '-top-[calc(var(--y)-var(--prev-step-y)-var(--height)-1rem)] bottom-[calc(var(--y)-var(--prev-step-y)-var(--height)+1rem)]'
// ].filter(Boolean).join(' '), ].filter(Boolean).join(' '),
// style: { style: {
// '--height': (inc.value * 4) + 'px', '--height': (inc.value * 4) + 'px',
// '--step-y': `${getStepY(steps.landing + 18)}px`, '--step-y': `${getStepY(steps.landing + 18)}px`,
// '--prev-step-y': `${getStepY(steps.landing + 10)}px` '--prev-step-y': `${getStepY(steps.landing + 10)}px`
// }, },
// inactive: isAfterStep(steps.landing + 15), inactive: isAfterStep(steps.landing + 15),
// children: [{ children: [{
// name: 'ULandingCTA', name: 'ULandingCTA',
// class: 'inset-4', class: 'inset-4',
// inactive: isAfterStep(steps.landing + 16), inactive: isAfterStep(steps.landing + 16),
// children: [{ children: [{
// slot: 'landing-cta', slot: 'landing-cta',
// class: 'inset-0' class: 'inset-0'
// }] }]
// }] }]
// }].filter(Boolean) }].filter(Boolean)
// }] }]
// : []) : [])
// const docsBlocks = computed(() => [isAfterStep(steps.docs) && { const docsBlocks = computed(() => [isAfterStep(steps.docs) && {
// name: 'UPage', name: 'UPage',
// to: '/pro/components/page', to: '/pro/components/page',
// class: 'inset-x-0 top-20 bottom-20', class: 'inset-x-0 top-20 bottom-20',
// inactive: isAfterStep(steps.docs + 1), inactive: isAfterStep(steps.docs + 1),
// children: [isAfterStep(steps.docs + 2) children: [isAfterStep(steps.docs + 2)
// ? { ? {
// name: 'UAside', name: 'UAside',
// to: '/pro/components/aside', to: '/pro/components/aside',
// class: 'left-4 inset-y-4 w-64', class: 'left-4 inset-y-4 w-64',
// inactive: isAfterStep(steps.docs + 3), inactive: isAfterStep(steps.docs + 3),
// children: [isAfterStep(steps.docs + 4) children: [isAfterStep(steps.docs + 4)
// ? { ? {
// slot: 'aside-top', slot: 'aside-top',
// class: 'inset-x-4 top-4' class: 'inset-x-4 top-4'
// } }
// : { : {
// name: '#top', name: '#top',
// class: 'inset-x-4 top-4 h-9' class: 'inset-x-4 top-4 h-9'
// }, isAfterStep(steps.docs + 5) }, isAfterStep(steps.docs + 5)
// ? { ? {
// name: 'UNavigationTree', name: 'UNavigationTree',
// to: '/pro/components/navigation-tree', to: '/pro/components/navigation-tree',
// class: ['inset-x-4 top-[4.25rem] bottom-4', isAfterStep(steps.docs + 6) && '!bg-transparent !border-0'].join(' '), class: ['inset-x-4 top-[4.25rem] bottom-4', isAfterStep(steps.docs + 6) && '!bg-transparent !border-0'].join(' '),
// inactive: isAfterStep(steps.docs + 6), inactive: isAfterStep(steps.docs + 6),
// children: [{ children: [{
// slot: 'aside-default', slot: 'aside-default',
// class: 'inset-0' class: 'inset-0'
// }] }]
// } }
// : { : {
// name: '#default', name: '#default',
// class: 'inset-x-4 top-[4.25rem] bottom-4' class: 'inset-x-4 top-[4.25rem] bottom-4'
// }] }]
// } }
// : { : {
// name: '#left', name: '#left',
// class: 'left-4 inset-y-4 w-64' class: 'left-4 inset-y-4 w-64'
// }, isAfterStep(steps.docs + 7) }, isAfterStep(steps.docs + 7)
// ? { ? {
// name: 'UPage', name: 'UPage',
// to: '/pro/components/page', to: '/pro/components/page',
// class: 'left-72 right-4 inset-y-4', class: 'left-72 right-4 inset-y-4',
// inactive: isAfterStep(steps.docs + 8), inactive: isAfterStep(steps.docs + 8),
// children: [...(isAfterStep(steps.docs + 9) children: [...(isAfterStep(steps.docs + 9)
// ? [{ ? [{
// name: 'UPageHeader', name: 'UPageHeader',
// to: '/pro/components/page-header', to: '/pro/components/page-header',
// class: 'top-4 left-4 right-72 h-32', class: 'top-4 left-4 right-72 h-32',
// inactive: isAfterStep(steps.docs + 10), inactive: isAfterStep(steps.docs + 10),
// children: [{ children: [{
// slot: 'page-header', slot: 'page-header',
// class: 'inset-4 justify-start' class: 'inset-4 justify-start'
// }] }]
// }, { }, {
// name: 'UPageBody', name: 'UPageBody',
// to: '/pro/components/page-body', to: '/pro/components/page-body',
// class: 'top-40 left-4 right-72 bottom-4 overflow-y-auto', class: 'top-40 left-4 right-72 bottom-4 overflow-y-auto',
// inactive: isAfterStep(steps.docs + 11), inactive: isAfterStep(steps.docs + 11),
// children: [{ children: [{
// slot: 'page-body', slot: 'page-body',
// class: 'inset-x-4 top-4 justify-start' class: 'inset-x-4 top-4 justify-start'
// }, isAfterStep(steps.docs + 12) }, isAfterStep(steps.docs + 12)
// ? { ? {
// slot: 'content-surround', slot: 'content-surround',
// class: 'bottom-4 inset-x-4 h-28' class: 'bottom-4 inset-x-4 h-28'
// } }
// : { : {
// name: 'UContentSurround', name: 'UContentSurround',
// to: '/pro/components/content-surround', to: '/pro/components/content-surround',
// class: 'bottom-4 inset-x-4 h-28', class: 'bottom-4 inset-x-4 h-28',
// inactive: false inactive: false
// }] }]
// }] }]
// : [{ : [{
// name: '#default', name: '#default',
// class: 'left-4 right-72 inset-y-4' class: 'left-4 right-72 inset-y-4'
// }]), isAfterStep(steps.docs + 13) }]), isAfterStep(steps.docs + 13)
// ? { ? {
// name: 'UContentToc', name: 'UContentToc',
// to: '/pro/components/content-toc', to: '/pro/components/content-toc',
// class: 'right-4 inset-y-4 w-64', class: 'right-4 inset-y-4 w-64',
// inactive: isAfterStep(steps.docs + 14), inactive: isAfterStep(steps.docs + 14),
// children: [{ children: [{
// slot: 'content-toc', slot: 'content-toc',
// class: 'inset-4 overflow-y-auto' class: 'inset-4 overflow-y-auto'
// }] }]
// } }
// : { : {
// name: '#right', name: '#right',
// class: 'right-4 inset-y-4 w-64' class: 'right-4 inset-y-4 w-64'
// }] }]
// } }
// : { : {
// name: '#default', name: '#default',
// class: 'left-72 right-4 inset-y-4' class: 'left-72 right-4 inset-y-4'
// }] }]
// }].filter(Boolean)) }].filter(Boolean))
// const blocks = computed(() => [isAfterStep(steps.header) && { const blocks = computed(() => [isAfterStep(steps.header) && {
// name: 'UHeader', name: 'UHeader',
// to: '/pro/components/header', to: '/pro/components/header',
// class: 'h-16 inset-x-0 top-0', class: 'h-16 inset-x-0 top-0',
// inactive: isAfterStep(steps.header + 1), inactive: isAfterStep(steps.header + 1),
// children: [isAfterStep(steps.header + 2) children: [isAfterStep(steps.header + 2)
// ? { ? {
// slot: 'header-left', slot: 'header-left',
// class: 'left-4 top-4' class: 'left-4 top-4'
// } }
// : { : {
// name: '#left', name: '#left',
// class: 'left-4 inset-y-4 w-64' class: 'left-4 inset-y-4 w-64'
// }, isAfterStep(steps.header + 3) }, isAfterStep(steps.header + 3)
// ? { ? {
// slot: 'header-center', slot: 'header-center',
// class: 'inset-x-72 top-5' class: 'inset-x-72 top-5'
// } }
// : { : {
// name: '#center', name: '#center',
// class: 'inset-x-72 inset-y-4' class: 'inset-x-72 inset-y-4'
// }, isAfterStep(steps.header + 4) }, isAfterStep(steps.header + 4)
// ? { ? {
// slot: 'header-right', slot: 'header-right',
// class: 'right-4 top-4' class: 'right-4 top-4'
// } }
// : { : {
// name: '#right', name: '#right',
// class: 'right-4 inset-y-4 w-64' class: 'right-4 inset-y-4 w-64'
// }] }]
// }, isAfterStep(steps.footer) && { }, isAfterStep(steps.footer) && {
// name: 'UFooter', name: 'UFooter',
// to: '/pro/components/footer', to: '/pro/components/footer',
// class: 'h-16 inset-x-0 bottom-0', class: 'h-16 inset-x-0 bottom-0',
// inactive: isAfterStep(steps.footer + 1), inactive: isAfterStep(steps.footer + 1),
// children: [isAfterStep(steps.footer + 2) children: [isAfterStep(steps.footer + 2)
// ? { ? {
// slot: 'footer-left', slot: 'footer-left',
// class: 'left-4 bottom-5' class: 'left-4 bottom-5'
// } }
// : { : {
// name: '#left', name: '#left',
// class: 'left-4 inset-y-4 w-64' class: 'left-4 inset-y-4 w-64'
// }, isAfterStep(steps.footer + 3) }, isAfterStep(steps.footer + 3)
// ? { ? {
// slot: 'footer-center', slot: 'footer-center',
// class: 'inset-x-72 bottom-5' class: 'inset-x-72 bottom-5'
// } }
// : { : {
// name: '#center', name: '#center',
// class: 'inset-x-72 inset-y-4' class: 'inset-x-72 inset-y-4'
// }, isAfterStep(steps.footer + 4) }, isAfterStep(steps.footer + 4)
// ? { ? {
// slot: 'footer-right', slot: 'footer-right',
// class: 'right-4 bottom-4' class: 'right-4 bottom-4'
// } }
// : { : {
// name: '#right', name: '#right',
// class: 'right-4 inset-y-4 w-64' class: 'right-4 inset-y-4 w-64'
// }] }]
// }, ...landingBlocks.value, ...docsBlocks.value].filter(Boolean)) }, ...landingBlocks.value, ...docsBlocks.value].filter(Boolean))
// Methods // Methods
// function isBeforeStep(i = 0) { function isBeforeStep(i = 0) {
// return y.value < (start.value + (i * inc.value)) return y.value < (start.value + (i * inc.value))
// } }
// function isAfterStep(i = 0) { function isAfterStep(i = 0) {
// return y.value >= (start.value + (i * inc.value)) return y.value >= (start.value + (i * inc.value))
// } }
// function getStepY(step: number) { function getStepY(step: number) {
// return start.value + (step * inc.value) return start.value + (step * inc.value)
// } }
// Hooks // Hooks
@@ -760,94 +760,94 @@ onMounted(() => {
// Slots Data // Slots Data
// const headerLinks = [{ const headerLinks = [{
// label: 'Documentation', label: 'Documentation',
// active: true active: true
// }, { }, {
// label: 'Playground' label: 'Playground'
// }, { }, {
// label: 'Roadmap' label: 'Roadmap'
// }, { }, {
// label: 'Pro' label: 'Pro'
// }] }]
// const navigationLinks = [{ const navigationLinks = [{
// label: 'Getting Started', label: 'Getting Started',
// children: [{ children: [{
// label: 'Introduction' label: 'Introduction'
// }, { }, {
// label: 'Installation', label: 'Installation',
// active: true active: true
// }, { }, {
// label: 'Theming' label: 'Theming'
// }, { }, {
// label: 'Shortcuts' label: 'Shortcuts'
// }, { }, {
// label: 'Examples' label: 'Examples'
// }, { }, {
// label: 'Roadmap' label: 'Roadmap'
// }] }]
// }, { }, {
// label: 'Elements', label: 'Elements',
// children: [{ children: [{
// label: 'Alert' label: 'Alert'
// }, { }, {
// label: 'Avatar' label: 'Avatar'
// }, { }, {
// label: 'Badge' label: 'Badge'
// }, { }, {
// label: 'Button' label: 'Button'
// }] }]
// }] }]
// const surround = [{ const surround = [{
// title: 'Introduction', title: 'Introduction',
// description: 'Fully styled and customizable components for Nuxt.', description: 'Fully styled and customizable components for Nuxt.',
// _path: '/' _path: '/'
// }, { }, {
// title: 'Theming', title: 'Theming',
// description: 'Learn how to customize the look and feel of the components.', description: 'Learn how to customize the look and feel of the components.',
// _path: '/' _path: '/'
// }] }]
// const md = ` const md = `
// ## Edge ## Edge
// To use the latest updates pushed on the [\`dev\`](https://github.com/nuxt/ui/tree/v2) branch, you can use \`@nuxt/ui-edge\`. To use the latest updates pushed on the [\`dev\`](https://github.com/nuxt/ui/tree/dev) branch, you can use \`@nuxt/ui-edge\`.
// ` `
// const toc = [{ const toc = [{
// id: 'quick-start', id: 'quick-start',
// depth: 2, depth: 2,
// text: 'Quick Start' text: 'Quick Start'
// }, { }, {
// id: 'intellisense', id: 'intellisense',
// depth: 2, depth: 2,
// text: 'IntelliSense' text: 'IntelliSense'
// }, { }, {
// id: 'options', id: 'options',
// depth: 2, depth: 2,
// text: 'Options' text: 'Options'
// }, { }, {
// id: 'edge', id: 'edge',
// depth: 2, depth: 2,
// text: 'Edge' text: 'Edge'
// }] }]
// const communityLinks = [{ const communityLinks = [{
// icon: 'i-heroicons-pencil-square', icon: 'i-heroicons-pencil-square',
// label: 'Edit this page' label: 'Edit this page'
// }, { }, {
// icon: 'i-heroicons-star', icon: 'i-heroicons-star',
// label: 'Star on GitHub', label: 'Star on GitHub',
// to: 'https://github.com/nuxt/ui', to: 'https://github.com/nuxt/ui',
// target: '_blank' target: '_blank'
// }, { }, {
// icon: 'i-heroicons-book-open', icon: 'i-heroicons-book-open',
// label: 'Nuxt documentation', label: 'Nuxt documentation',
// to: 'https://nuxt.com', to: 'https://nuxt.com',
// target: '_blank' target: '_blank'
// }] }]
</script> </script>
<style scoped lang="postcss"> <style scoped lang="postcss">

View File

@@ -41,8 +41,8 @@ if (!page.value) {
throw createError({ statusCode: 404, statusMessage: 'Page not found', fatal: true }) throw createError({ statusCode: 404, statusMessage: 'Page not found', fatal: true })
} }
const { data: releases } = await useFetch('/api/releases.json', { key: 'releases-list' }) const { data: releases } = await useFetch('/api/releases.json')
const { data: pulls } = await useLazyFetch('/api/pulls.json', { default: () => [], key: 'pulls-list' }) const { data: pulls } = await useLazyFetch('/api/pulls.json', { default: () => [] })
const dates = computed(() => { const dates = computed(() => {
const first = releases.value[releases.value.length - 1] const first = releases.value[releases.value.length - 1]

View File

@@ -1 +1,2 @@
user-agent: * user-agent: *
disallow: /dev/*

View File

@@ -1,29 +1,20 @@
{ {
"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": "2.22.1", "version": "2.19.2",
"packageManager": "pnpm@10.13.1", "packageManager": "pnpm@9.12.3",
"repository": { "repository": "nuxt/ui",
"type": "git", "homepage": "https://ui.nuxt.com",
"url": "git+https://github.com/nuxt/ui.git"
},
"homepage": "https://ui2.nuxt.com",
"type": "module", "type": "module",
"license": "MIT", "license": "MIT",
"exports": { "exports": {
".": { ".": {
"types": "./dist/module.d.mts", "import": "./dist/module.mjs",
"import": "./dist/module.mjs" "require": "./dist/module.cjs"
} }
}, },
"typesVersions": { "main": "./dist/module.cjs",
"*": { "types": "./dist/types.d.ts",
".": [
"./dist/module.d.mts"
]
}
},
"main": "./dist/module.mjs",
"files": [ "files": [
"dist" "dist"
], ],
@@ -41,78 +32,56 @@
"test": "vitest" "test": "vitest"
}, },
"dependencies": { "dependencies": {
"@headlessui/tailwindcss": "^0.2.2", "@headlessui/tailwindcss": "^0.2.1",
"@headlessui/vue": "^1.7.23", "@headlessui/vue": "^1.7.23",
"@iconify-json/heroicons": "^1.2.2", "@iconify-json/heroicons": "^1.2.1",
"@nuxt/icon": "^1.15.0", "@nuxt/icon": "^1.7.2",
"@nuxt/kit": "^4.0.1", "@nuxt/kit": "^3.14.159",
"@nuxtjs/color-mode": "^3.5.2", "@nuxtjs/color-mode": "^3.5.2",
"@nuxtjs/tailwindcss": "^6.14.0", "@nuxtjs/tailwindcss": "^6.12.2",
"@popperjs/core": "^2.11.8", "@popperjs/core": "^2.11.8",
"@standard-schema/spec": "^1.0.0",
"@tailwindcss/aspect-ratio": "^0.4.2", "@tailwindcss/aspect-ratio": "^0.4.2",
"@tailwindcss/container-queries": "^0.1.1", "@tailwindcss/container-queries": "^0.1.1",
"@tailwindcss/forms": "^0.5.10", "@tailwindcss/forms": "^0.5.9",
"@tailwindcss/typography": "^0.5.16", "@tailwindcss/typography": "^0.5.15",
"@vueuse/core": "^13.5.0", "@vueuse/core": "^11.2.0",
"@vueuse/integrations": "^13.5.0", "@vueuse/integrations": "^11.2.0",
"@vueuse/math": "^13.5.0", "@vueuse/math": "^11.2.0",
"defu": "^6.1.4", "defu": "^6.1.4",
"fuse.js": "^7.1.0", "fuse.js": "^7.0.0",
"ohash": "^2.0.11", "ohash": "^1.1.4",
"pathe": "^2.0.3", "pathe": "^1.1.2",
"scule": "^1.3.0", "scule": "^1.3.0",
"tailwind-merge": "^2.6.0", "tailwind-merge": "^2.5.4",
"tailwindcss": "^3.4.17" "tailwindcss": "^3.4.14"
}, },
"devDependencies": { "devDependencies": {
"@nuxt/eslint-config": "^1.6.0", "@nuxt/eslint-config": "^0.6.1",
"@nuxt/module-builder": "^1.0.1", "@nuxt/module-builder": "^0.8.4",
"@nuxt/test-utils": "^3.19.2", "@nuxt/test-utils": "^3.14.4",
"@release-it/conventional-changelog": "^10.0.1", "@release-it/conventional-changelog": "^9.0.2",
"@vue/test-utils": "^2.4.6", "@vue/test-utils": "^2.4.6",
"eslint": "^9.31.0", "eslint": "^9.14.0",
"happy-dom": "^18.0.1", "happy-dom": "^14.12.3",
"nuxt": "^4.0.1", "joi": "^17.13.3",
"release-it": "^19.0.4", "nuxt": "^3.14.159",
"typescript": "^5.8.3", "release-it": "^17.10.0",
"vitest": "^3.2.4", "superstruct": "^2.0.2",
"unbuild": "^2.0.0",
"valibot": "^0.42.1",
"valibot30": "npm:valibot@0.30.0",
"valibot31": "npm:valibot@0.31.0",
"vitest": "^2.1.4",
"vitest-environment-nuxt": "^1.0.1", "vitest-environment-nuxt": "^1.0.1",
"vue-tsc": "^3.0.3" "vue-tsc": "^2.1.10",
}, "yup": "^1.4.0",
"peerDependencies": { "zod": "^3.23.8"
"joi": "^17.13.0",
"superstruct": "^2.0.0",
"valibot": "^1.0.0",
"yup": "^1.6.0",
"zod": "^3.24.0"
},
"peerDependenciesMeta": {
"joi": {
"optional": true
},
"valibot": {
"optional": true
},
"superstruct": {
"optional": true
},
"yup": {
"optional": true
},
"zod": {
"optional": true
}
}, },
"resolutions": { "resolutions": {
"@nuxt/ui": "workspace:*", "@nuxt/ui": "workspace:*",
"@nuxt/content": "2.13.2", "@nuxt/content": "2.13.2",
"@nuxtjs/mdc": "0.9.0", "@nuxtjs/mdc": "0.9.0",
"@vueuse/core": "^13.5.0", "nuxt": "3.13.2",
"@vueuse/integrations": "^13.5.0", "@nuxt/kit": "3.13.2"
"@vueuse/math": "^13.5.0",
"@vueuse/nuxt": "13.1.0",
"unimport": "4.1.1",
"chokidar": "3.6.0"
} }
} }

View File

@@ -9,6 +9,6 @@
}, },
"dependencies": { "dependencies": {
"@nuxt/ui": "latest", "@nuxt/ui": "latest",
"nuxt": "^4.0.1" "nuxt": "^3.14.159"
} }
} }

12910
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,10 +1,4 @@
packages: packages:
- ./ - "./"
- docs - "docs"
- playground - "playground"
ignoredBuiltDependencies:
- '@parcel/watcher'
- esbuild
- sharp
- vue-demi

View File

@@ -5,7 +5,12 @@
"lockFileMaintenance": { "lockFileMaintenance": {
"enabled": true "enabled": true
}, },
"baseBranches": ["v2", "v3"], "ignoreDeps": [
"happy-dom",
"valibot30",
"valibot31"
],
"baseBranches": ["dev", "v3"],
"packageRules": [{ "packageRules": [{
"matchBaseBranches": ["v3"], "matchBaseBranches": ["v3"],
"labels": ["v3"] "labels": ["v3"]
@@ -19,6 +24,5 @@
}, { }, {
"matchDepTypes": ["resolutions"], "matchDepTypes": ["resolutions"],
"enabled": false "enabled": false
}], }]
"postUpdateOptions": ["pnpmDedupe"]
} }

36
scripts/bump-edge.ts Normal file
View File

@@ -0,0 +1,36 @@
import { promises as fsp } from 'node:fs'
import { resolve } from 'node:path'
import { execSync } from 'node:child_process'
async function loadPackage(dir: string) {
const pkgPath = resolve(dir, 'package.json')
const data = JSON.parse(await fsp.readFile(pkgPath, 'utf-8').catch(() => '{}'))
const save = () => fsp.writeFile(pkgPath, JSON.stringify(data, null, 2) + '\n')
return {
dir,
data,
save
}
}
async function main() {
const pkg = await loadPackage(process.cwd())
const commit = execSync('git rev-parse --short HEAD').toString('utf-8').trim()
const date = Math.round(Date.now() / (1000 * 60))
pkg.data.name = `${pkg.data.name}-edge`
pkg.data.version = `${pkg.data.version}-${date}.${commit}`
pkg.save()
}
main().catch((err) => {
console.error(err)
process.exit(1)
})

19
scripts/release-edge.sh Executable file
View File

@@ -0,0 +1,19 @@
#!/bin/bash
# Restore all git changes
git restore -s@ -SW -- .
# Bump versions to edge
pnpm jiti ./scripts/bump-edge
# Update token
if [[ ! -z ${NODE_AUTH_TOKEN} ]] ; then
echo "//registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}" >> ~/.npmrc
echo "registry=https://registry.npmjs.org/" >> ~/.npmrc
echo "always-auth=true" >> ~/.npmrc
npm whoami
fi
# Release package
echo "Publishing @nuxt/ui"
npm publish -q --access public

View File

@@ -13,4 +13,4 @@ fi
# Release package # Release package
echo "Publishing @nuxt/ui" echo "Publishing @nuxt/ui"
npm publish --access public --no-tag npm publish -q --access public

View File

@@ -1,6 +1,5 @@
import { createRequire } from 'node:module' import { createRequire } from 'node:module'
import { defineNuxtModule, installModule, addComponentsDir, addImportsDir, createResolver, addPlugin } from '@nuxt/kit' import { defineNuxtModule, installModule, addComponentsDir, addImportsDir, createResolver, addPlugin } from '@nuxt/kit'
import type { ConfigExtension, DefaultClassGroupIds, DefaultThemeGroupIds } from 'tailwind-merge'
import { name, version } from '../package.json' import { name, version } from '../package.json'
import createTemplates from './templates' import createTemplates from './templates'
import type * as config from './runtime/ui.config' import type * as config from './runtime/ui.config'
@@ -21,7 +20,6 @@ type UI = {
gray?: string gray?: string
colors?: string[] colors?: string[]
strategy?: Strategy strategy?: Strategy
tailwindMerge?: ConfigExtension<DefaultClassGroupIds, DefaultThemeGroupIds>
[key: string]: any [key: string]: any
} & DeepPartial<typeof config, string | number | boolean> } & DeepPartial<typeof config, string | number | boolean>
@@ -43,11 +41,6 @@ export interface ModuleOptions {
*/ */
global?: boolean global?: boolean
/**
* @default true
*/
colorMode?: boolean
safelistColors?: string[] safelistColors?: string[]
/** /**
* Disables the global css styles added by the module. * Disables the global css styles added by the module.
@@ -66,7 +59,6 @@ export default defineNuxtModule<ModuleOptions>({
}, },
defaults: { defaults: {
prefix: 'U', prefix: 'U',
colorMode: true,
safelistColors: ['primary'], safelistColors: ['primary'],
disableGlobalStyles: false disableGlobalStyles: false
}, },
@@ -89,9 +81,7 @@ export default defineNuxtModule<ModuleOptions>({
// Modules // Modules
await installModule('@nuxt/icon') await installModule('@nuxt/icon')
if (options.colorMode) { await installModule('@nuxtjs/color-mode', { classSuffix: '' })
await installModule('@nuxtjs/color-mode', { classSuffix: '' })
}
await installTailwind(options, nuxt, resolve) await installTailwind(options, nuxt, resolve)
// Plugins // Plugins

View File

@@ -18,7 +18,7 @@
:class="[ui.th.base, ui.th.padding, ui.th.color, ui.th.font, ui.th.size, column.key === 'select' && ui.checkbox.padding, column.class]" :class="[ui.th.base, ui.th.padding, ui.th.color, ui.th.font, ui.th.size, column.key === 'select' && ui.checkbox.padding, column.class]"
:aria-sort="getAriaSort(column)" :aria-sort="getAriaSort(column)"
> >
<slot v-if="!singleSelect && modelValue && column.key === 'select'" name="select-header" :indeterminate="indeterminate" :checked="isAllRowChecked" :change="onChange"> <slot v-if="!singleSelect && modelValue && (column.key === 'select' || shouldRenderColumnInFirstPlace(index, 'select'))" name="select-header" :indeterminate="indeterminate" :checked="isAllRowChecked" :change="onChange">
<UCheckbox <UCheckbox
:model-value="isAllRowChecked" :model-value="isAllRowChecked"
:indeterminate="indeterminate" :indeterminate="indeterminate"
@@ -93,19 +93,19 @@
/> />
</td> </td>
<td v-for="(column, subIndex) in columns" :key="subIndex" :class="[ui.td.base, ui.td.padding, ui.td.color, ui.td.font, ui.td.size, column?.rowClass, row[column.key]?.class, column.key === 'select' && ui.checkbox.padding]"> <td v-for="(column, subIndex) in columns" :key="subIndex" :class="[ui.td.base, ui.td.padding, ui.td.color, ui.td.font, ui.td.size, column?.rowClass, row[column.key]?.class, column.key === 'select' && ui.checkbox.padding]">
<!-- This is a workaround: Since the @change event doesn't bubble up naturally, we need to wrap it in another element and use @click.capture.stop to simulate event bubbling behavior --> <slot v-if="modelValue && (column.key === 'select' || shouldRenderColumnInFirstPlace(subIndex, 'select')) " name="select-data" :checked="isSelected(row)" :change="(ev: boolean) => onChangeCheckbox(ev, row)">
<span v-if="modelValue && column.key === 'select' " @click.capture.stop="() => {}"> <UCheckbox
<slot name="select-data" :checked="isSelected(row)" :change="(ev: boolean) => onChangeCheckbox(ev, row)"> :model-value="isSelected(row)"
<UCheckbox v-bind="ui.default.checkbox"
:model-value="isSelected(row)" aria-label="Select row"
v-bind="ui.default.checkbox" @change="onChangeCheckbox($event, row)"
aria-label="Select row" @click.capture.stop="() => onSelect(row)"
@change="onChangeCheckbox($event, row)" />
/> </slot>
</slot>
</span>
<slot <slot
v-else v-else
:key="retriggerSlot"
:name="`${column.key}-data`" :name="`${column.key}-data`"
:column="column" :column="column"
:row="row" :row="row"
@@ -133,18 +133,18 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { computed, defineComponent, toRaw, toRef } from 'vue' import { computed, defineComponent, ref, toRaw, toRef, watch } from 'vue'
import type { PropType, AriaAttributes } from 'vue' import type { PropType, AriaAttributes } from 'vue'
import { upperFirst } from 'scule' import { upperFirst } from 'scule'
import { defu } from 'defu' import { defu } from 'defu'
import { useVModel } from '@vueuse/core' import { useVModel } from '@vueuse/core'
import { isEqual } from 'ohash/utils' import { isEqual } from 'ohash'
import UIcon from '../elements/Icon.vue' import UIcon from '../elements/Icon.vue'
import UButton from '../elements/Button.vue' import UButton from '../elements/Button.vue'
import UProgress from '../elements/Progress.vue' import UProgress from '../elements/Progress.vue'
import UCheckbox from '../forms/Checkbox.vue' import UCheckbox from '../forms/Checkbox.vue'
import { useUI } from '../../composables/useUI' import { useUI } from '../../composables/useUI'
import { get, mergeConfig } from '../../utils' import { mergeConfig, get } from '../../utils'
import type { TableRow, TableColumn, Strategy, Button, ProgressColor, ProgressAnimation, DeepPartial, Expanded } from '../../types/index' import type { TableRow, TableColumn, Strategy, Button, ProgressColor, ProgressAnimation, DeepPartial, Expanded } from '../../types/index'
// @ts-expect-error // @ts-expect-error
import appConfig from '#build/app.config' import appConfig from '#build/app.config'
@@ -274,30 +274,7 @@ export default defineComponent({
setup(props, { emit, attrs: $attrs }) { setup(props, { emit, attrs: $attrs }) {
const { ui, attrs } = useUI('table', toRef(props, 'ui'), config, toRef(props, 'class')) const { ui, attrs } = useUI('table', toRef(props, 'ui'), config, toRef(props, 'class'))
const columns = computed(() => { const columns = computed(() => props.columns ?? Object.keys(props.rows[0] ?? {}).map(key => ({ key, label: upperFirst(key), sortable: false, class: undefined, sort: defaultSort }) as TableColumn))
const defaultColumns = props.columns ?? (
Object.keys(props.rows[0]).map(key => ({
key,
label: upperFirst(key),
sortable: false,
class: undefined,
sort: defaultSort
}))
) as TableColumn[]
const hasColumnSelect = defaultColumns.find(v => v.key === 'select')
if (hasColumnSelect || !props.modelValue) {
return defaultColumns
}
return [{
key: 'select',
sortable: false,
class: undefined,
sort: defaultSort
}, ...defaultColumns]
})
const sort = useVModel(props, 'sort', emit, { passive: true, defaultValue: defu({}, props.sort, { column: null, direction: 'asc' }) }) const sort = useVModel(props, 'sort', emit, { passive: true, defaultValue: defu({}, props.sort, { column: null, direction: 'asc' }) })
const expand = useVModel(props, 'expand', emit, { const expand = useVModel(props, 'expand', emit, {
@@ -308,6 +285,8 @@ export default defineComponent({
}) })
}) })
const retriggerSlot = ref(null)
const savedSort = { column: sort.value.column, direction: null } const savedSort = { column: sort.value.column, direction: null }
const rows = computed(() => { const rows = computed(() => {
@@ -443,7 +422,8 @@ export default defineComponent({
if (checked) { if (checked) {
selected.value = props.singleSelect ? [row] : [...selected.value, row] selected.value = props.singleSelect ? [row] : [...selected.value, row]
} else { } else {
selected.value = selected.value.filter(value => !compare(toRaw(value), toRaw(row))) const index = selected.value.findIndex(item => compare(item, row))
selected.value.splice(index, 1)
} }
} }
@@ -455,6 +435,13 @@ export default defineComponent({
return expand.value?.openedRows ? expand.value.openedRows.some(openedRow => compare(openedRow, row)) : false return expand.value?.openedRows ? expand.value.openedRows.some(openedRow => compare(openedRow, row)) : false
} }
function shouldRenderColumnInFirstPlace(index: number, key: string) {
if (!props.columns) {
return index === 0
}
return index === 0 && !props.columns.find(col => col.key === key)
}
function toggleOpened(row: TableRow) { function toggleOpened(row: TableRow) {
expand.value = { expand.value = {
openedRows: isExpanded(row) ? expand.value.openedRows.filter(v => !compare(v, row)) : props.multipleExpand ? [...expand.value.openedRows, row] : [row], openedRows: isExpanded(row) ? expand.value.openedRows.filter(v => !compare(v, row)) : props.multipleExpand ? [...expand.value.openedRows, row] : [row],
@@ -482,6 +469,12 @@ export default defineComponent({
return undefined return undefined
} }
watch(rows, () => {
retriggerSlot.value = new Date()
}, {
deep: true
})
return { return {
// eslint-disable-next-line vue/no-dupe-keys // eslint-disable-next-line vue/no-dupe-keys
ui, ui,
@@ -508,7 +501,9 @@ export default defineComponent({
getRowData, getRowData,
toggleOpened, toggleOpened,
getAriaSort, getAriaSort,
isExpanded isExpanded,
shouldRenderColumnInFirstPlace,
retriggerSlot
} }
} }
}) })

View File

@@ -126,7 +126,7 @@ export default defineComponent({
default: () => ({}) default: () => ({})
} }
}, },
emits: ['open', 'close'], emits: ['open'],
setup(props, { emit }) { setup(props, { emit }) {
const { ui, attrs } = useUI('accordion', toRef(props, 'ui'), config, toRef(props, 'class')) const { ui, attrs } = useUI('accordion', toRef(props, 'ui'), config, toRef(props, 'class'))
@@ -142,8 +142,6 @@ export default defineComponent({
if (!isOpenBefore && isOpenAfter) { if (!isOpenBefore && isOpenAfter) {
emit('open', index) emit('open', index)
} else if (isOpenBefore && !isOpenAfter) {
emit('close', index)
} }
} }
}, { immediate: true }) }, { immediate: true })

View File

@@ -14,7 +14,7 @@
{{ title }} {{ title }}
</slot> </slot>
</p> </p>
<div v-if="description || $slots.description" :class="twMerge(ui.description, !title && !$slots.title && ui.descriptionOnly)"> <div v-if="description || $slots.description" :class="twMerge(ui.description, !title && !$slots.title && 'mt-0 leading-5')">
<slot name="description" :description="description"> <slot name="description" :description="description">
{{ description }} {{ description }}
</slot> </slot>
@@ -42,13 +42,13 @@
<script lang="ts"> <script lang="ts">
import { computed, toRef, defineComponent } from 'vue' import { computed, toRef, defineComponent } from 'vue'
import type { PropType } from 'vue' import type { PropType } from 'vue'
import { twJoin } from 'tailwind-merge' import { twMerge, twJoin } from 'tailwind-merge'
import UIcon from '../elements/Icon.vue' import UIcon from '../elements/Icon.vue'
import UAvatar from '../elements/Avatar.vue' import UAvatar from '../elements/Avatar.vue'
import UButton from '../elements/Button.vue' import UButton from '../elements/Button.vue'
import { useUI } from '../../composables/useUI' import { useUI } from '../../composables/useUI'
import type { Avatar, Button, AlertColor, AlertVariant, AlertAction, Strategy, DeepPartial } from '../../types/index' import type { Avatar, Button, AlertColor, AlertVariant, AlertAction, Strategy, DeepPartial } from '../../types/index'
import { mergeConfig, twMerge } from '../../utils' import { mergeConfig } from '../../utils'
// @ts-expect-error // @ts-expect-error
import appConfig from '#build/app.config' import appConfig from '#build/app.config'
import { alert } from '#ui/ui.config' import { alert } from '#ui/ui.config'

View File

@@ -23,10 +23,10 @@
<script lang="ts"> <script lang="ts">
import { defineComponent, ref, computed, toRef, watch } from 'vue' import { defineComponent, ref, computed, toRef, watch } from 'vue'
import type { PropType } from 'vue' import type { PropType } from 'vue'
import { twJoin } from 'tailwind-merge' import { twMerge, twJoin } from 'tailwind-merge'
import UIcon from '../elements/Icon.vue' import UIcon from '../elements/Icon.vue'
import { useUI } from '../../composables/useUI' import { useUI } from '../../composables/useUI'
import { mergeConfig, twMerge } from '../../utils' import { mergeConfig } from '../../utils'
import type { AvatarSize, AvatarChipColor, AvatarChipPosition, Strategy, DeepPartial } from '../../types/index' import type { AvatarSize, AvatarChipColor, AvatarChipPosition, Strategy, DeepPartial } from '../../types/index'
// @ts-expect-error // @ts-expect-error
import appConfig from '#build/app.config' import appConfig from '#build/app.config'

View File

@@ -1,8 +1,8 @@
import { h, cloneVNode, computed, toRef, defineComponent } from 'vue' import { h, cloneVNode, computed, toRef, defineComponent } from 'vue'
import type { PropType } from 'vue' import type { PropType } from 'vue'
import { twJoin } from 'tailwind-merge' import { twMerge, twJoin } from 'tailwind-merge'
import { useUI } from '../../composables/useUI' import { useUI } from '../../composables/useUI'
import { getSlotsChildren, mergeConfig, twMerge } from '../../utils' import { mergeConfig, getSlotsChildren } from '../../utils'
import type { AvatarSize, DeepPartial, Strategy } from '../../types/index' import type { AvatarSize, DeepPartial, Strategy } from '../../types/index'
import UAvatar from './Avatar.vue' import UAvatar from './Avatar.vue'
// @ts-expect-error // @ts-expect-error

View File

@@ -1,28 +1,15 @@
<template> <template>
<span :class="badgeClass" v-bind="attrs"> <span :class="badgeClass" v-bind="attrs">
<slot name="leading"> <slot>{{ label }}</slot>
<UIcon v-if="isLeading && leadingIconName" :name="leadingIconName" :class="leadingIconClass" aria-hidden="true" />
</slot>
<slot>
<span v-if="label !== undefined && label !== null">
{{ label }}
</span>
</slot>
<slot name="trailing">
<UIcon v-if="isTrailing && trailingIconName" :name="trailingIconName" :class="trailingIconClass" aria-hidden="true" />
</slot>
</span> </span>
</template> </template>
<script lang="ts"> <script lang="ts">
import { computed, toRef, defineComponent } from 'vue' import { computed, toRef, defineComponent } from 'vue'
import type { PropType } from 'vue' import type { PropType } from 'vue'
import { twJoin } from 'tailwind-merge' import { twMerge, twJoin } from 'tailwind-merge'
import UIcon from '../elements/Icon.vue'
import { useUI } from '../../composables/useUI' import { useUI } from '../../composables/useUI'
import { mergeConfig, twMerge } from '../../utils' import { mergeConfig } from '../../utils'
import { useInjectButtonGroup } from '../../composables/useButtonGroup' import { useInjectButtonGroup } from '../../composables/useButtonGroup'
import type { BadgeColor, BadgeSize, BadgeVariant, DeepPartial, Strategy } from '../../types/index' import type { BadgeColor, BadgeSize, BadgeVariant, DeepPartial, Strategy } from '../../types/index'
// @ts-expect-error // @ts-expect-error
@@ -32,9 +19,6 @@ import { badge } from '#ui/ui.config'
const config = mergeConfig<typeof badge>(appConfig.ui.strategy, appConfig.ui.badge, badge) const config = mergeConfig<typeof badge>(appConfig.ui.strategy, appConfig.ui.badge, badge)
export default defineComponent({ export default defineComponent({
components: {
UIcon
},
inheritAttrs: false, inheritAttrs: false,
props: { props: {
size: { size: {
@@ -65,26 +49,6 @@ export default defineComponent({
type: [String, Number], type: [String, Number],
default: null default: null
}, },
icon: {
type: String,
default: null
},
leadingIcon: {
type: String,
default: null
},
trailingIcon: {
type: String,
default: null
},
trailing: {
type: Boolean,
default: false
},
leading: {
type: Boolean,
default: false
},
class: { class: {
type: [String, Object, Array] as PropType<any>, type: [String, Object, Array] as PropType<any>,
default: () => '' default: () => ''
@@ -99,14 +63,6 @@ export default defineComponent({
const { size, rounded } = useInjectButtonGroup({ ui, props }) const { size, rounded } = useInjectButtonGroup({ ui, props })
const isLeading = computed(() => {
return (props.icon && props.leading) || (props.icon && !props.trailing) || !props.trailing || props.leadingIcon
})
const isTrailing = computed(() => {
return (props.icon && props.trailing) || props.trailing || props.trailingIcon
})
const badgeClass = computed(() => { const badgeClass = computed(() => {
const variant = ui.value.color?.[props.color as string]?.[props.variant as string] || ui.value.variant[props.variant] const variant = ui.value.color?.[props.color as string]?.[props.variant as string] || ui.value.variant[props.variant]
@@ -115,42 +71,13 @@ export default defineComponent({
ui.value.font, ui.value.font,
rounded.value, rounded.value,
ui.value.size[size.value], ui.value.size[size.value],
ui.value.gap[size.value],
variant?.replaceAll('{color}', props.color) variant?.replaceAll('{color}', props.color)
), props.class) ), props.class)
}) })
const leadingIconName = computed(() => {
return props.leadingIcon || props.icon
})
const trailingIconName = computed(() => {
return props.trailingIcon || props.icon
})
const leadingIconClass = computed(() => {
return twJoin(
ui.value.icon.base,
ui.value.icon.size[size.value]
)
})
const trailingIconClass = computed(() => {
return twJoin(
ui.value.icon.base,
ui.value.icon.size[size.value]
)
})
return { return {
attrs, attrs,
isLeading, badgeClass
isTrailing,
badgeClass,
leadingIconName,
trailingIconName,
leadingIconClass,
trailingIconClass
} }
} }
}) })

View File

@@ -5,7 +5,7 @@
</slot> </slot>
<slot> <slot>
<span v-if="label !== undefined && label !== null" :class="[truncate ? ui.truncate : '']"> <span v-if="label" :class="[truncate ? ui.truncate : '']">
{{ label }} {{ label }}
</span> </span>
</slot> </slot>
@@ -19,11 +19,11 @@
<script lang="ts"> <script lang="ts">
import { computed, defineComponent, toRef } from 'vue' import { computed, defineComponent, toRef } from 'vue'
import type { PropType } from 'vue' import type { PropType } from 'vue'
import { twJoin } from 'tailwind-merge' import { twMerge, twJoin } from 'tailwind-merge'
import UIcon from '../elements/Icon.vue' import UIcon from '../elements/Icon.vue'
import ULink from '../elements/Link.vue' import ULink from '../elements/Link.vue'
import { useUI } from '../../composables/useUI' import { useUI } from '../../composables/useUI'
import { getNuxtLinkProps, mergeConfig, nuxtLinkProps, twMerge } from '../../utils' import { mergeConfig, nuxtLinkProps, getNuxtLinkProps } from '../../utils'
import { useInjectButtonGroup } from '../../composables/useButtonGroup' import { useInjectButtonGroup } from '../../composables/useButtonGroup'
import type { ButtonColor, ButtonSize, ButtonVariant, DeepPartial, Strategy } from '../../types/index' import type { ButtonColor, ButtonSize, ButtonVariant, DeepPartial, Strategy } from '../../types/index'
// @ts-expect-error // @ts-expect-error

View File

@@ -1,8 +1,8 @@
import { h, computed, toRef, defineComponent } from 'vue' import { h, computed, toRef, defineComponent } from 'vue'
import type { PropType } from 'vue' import type { PropType } from 'vue'
import { twJoin } from 'tailwind-merge' import { twMerge, twJoin } from 'tailwind-merge'
import { useUI } from '../../composables/useUI' import { useUI } from '../../composables/useUI'
import { getSlotsChildren, mergeConfig, twMerge } from '../../utils' import { mergeConfig, getSlotsChildren } from '../../utils'
import { useProvideButtonGroup } from '../../composables/useButtonGroup' import { useProvideButtonGroup } from '../../composables/useButtonGroup'
import type { ButtonSize, DeepPartial, Strategy } from '../../types/index' import type { ButtonSize, DeepPartial, Strategy } from '../../types/index'
// @ts-expect-error // @ts-expect-error

View File

@@ -58,8 +58,9 @@
<script lang="ts"> <script lang="ts">
import { ref, toRef, computed, defineComponent } from 'vue' import { ref, toRef, computed, defineComponent } from 'vue'
import type { PropType } from 'vue' import type { PropType } from 'vue'
import { twMerge } from 'tailwind-merge'
import { useScroll, useResizeObserver, useElementSize } from '@vueuse/core' import { useScroll, useResizeObserver, useElementSize } from '@vueuse/core'
import { mergeConfig, twMerge } from '../../utils' import { mergeConfig } from '../../utils'
import UButton from '../elements/Button.vue' import UButton from '../elements/Button.vue'
import type { Strategy, Button, DeepPartial } from '../../types/index' import type { Strategy, Button, DeepPartial } from '../../types/index'
import { useUI } from '../../composables/useUI' import { useUI } from '../../composables/useUI'
@@ -105,7 +106,7 @@ export default defineComponent({
default: () => '' default: () => ''
}, },
ui: { ui: {
type: Object as PropType<DeepPartial<typeof config> & { strategy?: Strategy }>, type: Object as PropType<DeepPartial<typeof config & { strategy?: Strategy }>>,
default: undefined default: undefined
} }
}, },

View File

@@ -60,13 +60,13 @@ import { defineComponent, ref, computed, watch, toRef, onMounted, resolveCompone
import type { PropType } from 'vue' import type { PropType } from 'vue'
import { Menu as HMenu, MenuButton as HMenuButton, MenuItems as HMenuItems, MenuItem as HMenuItem, provideUseId } from '@headlessui/vue' import { Menu as HMenu, MenuButton as HMenuButton, MenuItems as HMenuItems, MenuItem as HMenuItem, provideUseId } from '@headlessui/vue'
import { defu } from 'defu' import { defu } from 'defu'
import { twJoin } from 'tailwind-merge' import { twMerge, twJoin } from 'tailwind-merge'
import UIcon from '../elements/Icon.vue' import UIcon from '../elements/Icon.vue'
import UAvatar from '../elements/Avatar.vue' import UAvatar from '../elements/Avatar.vue'
import UKbd from '../elements/Kbd.vue' import UKbd from '../elements/Kbd.vue'
import { useUI } from '../../composables/useUI' import { useUI } from '../../composables/useUI'
import { usePopper } from '../../composables/usePopper' import { usePopper } from '../../composables/usePopper'
import { getNuxtLinkProps, mergeConfig, twMerge } from '../../utils' import { mergeConfig, getNuxtLinkProps } from '../../utils'
import type { DeepPartial, DropdownItem, PopperOptions, Strategy } from '../../types/index' import type { DeepPartial, DropdownItem, PopperOptions, Strategy } from '../../types/index'
// @ts-expect-error // @ts-expect-error
import appConfig from '#build/app.config' import appConfig from '#build/app.config'

View File

@@ -7,9 +7,9 @@
<script lang="ts"> <script lang="ts">
import { toRef, defineComponent, computed } from 'vue' import { toRef, defineComponent, computed } from 'vue'
import type { PropType } from 'vue' import type { PropType } from 'vue'
import { twJoin } from 'tailwind-merge' import { twMerge, twJoin } from 'tailwind-merge'
import { useUI } from '../../composables/useUI' import { useUI } from '../../composables/useUI'
import { mergeConfig, twMerge } from '../../utils' import { mergeConfig } from '../../utils'
import type { DeepPartial, KbdSize, Strategy } from '../../types/index' import type { DeepPartial, KbdSize, Strategy } from '../../types/index'
// @ts-expect-error // @ts-expect-error
import appConfig from '#build/app.config' import appConfig from '#build/app.config'

View File

@@ -32,9 +32,8 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { isEqual, diff } from 'ohash/utils' import { isEqual } from 'ohash'
import { defineComponent } from 'vue' import { defineComponent } from 'vue'
import type { PropType } from 'vue'
import { nuxtLinkProps } from '../../utils' import { nuxtLinkProps } from '../../utils'
export default defineComponent({ export default defineComponent({
@@ -62,7 +61,7 @@ export default defineComponent({
default: false default: false
}, },
exactQuery: { exactQuery: {
type: [Boolean, String] as PropType<boolean | 'partial'>, type: Boolean,
default: false default: false
}, },
exactHash: { exactHash: {
@@ -75,25 +74,9 @@ export default defineComponent({
} }
}, },
setup(props) { setup(props) {
function isPartiallyEqual(item1: any, item2: any) {
const diffedKeys = diff(item1, item2).reduce((filtered, q) => {
if (q.type === 'added') {
filtered.add(q.key)
}
return filtered
}, new Set<string>())
const item1Filtered = Object.fromEntries(Object.entries(item1).filter(([key]) => !diffedKeys.has(key)))
const item2Filtered = Object.fromEntries(Object.entries(item2).filter(([key]) => !diffedKeys.has(key)))
return isEqual(item1Filtered, item2Filtered)
}
function resolveLinkClass(route, $route, { isActive, isExactActive }: { isActive: boolean, isExactActive: boolean }) { function resolveLinkClass(route, $route, { isActive, isExactActive }: { isActive: boolean, isExactActive: boolean }) {
if (props.exactQuery === 'partial') { if (props.exactQuery && !isEqual(route.query, $route.query)) {
if (!isPartiallyEqual(route.query, $route.query)) return props.inactiveClass return props.inactiveClass
} else if (props.exactQuery === true) {
if (!isEqual(route.query, $route.query)) return props.inactiveClass
} }
if (props.exactHash && route.hash !== $route.hash) { if (props.exactHash && route.hash !== $route.hash) {
return props.inactiveClass return props.inactiveClass

View File

@@ -46,6 +46,10 @@ export default defineComponent({
UIcon UIcon
}, },
inheritAttrs: false, inheritAttrs: false,
slots: Object as SlotsType<{
indicator?: { percent: number, value: number }
label?: { percent: number, value: number }
}>,
props: { props: {
value: { value: {
type: Number, type: Number,
@@ -94,10 +98,6 @@ export default defineComponent({
default: () => ({}) default: () => ({})
} }
}, },
slots: Object as SlotsType<{
indicator?: { percent: number, value: number }
label?: { percent: number, value: number }
}>,
setup(props) { setup(props) {
const { ui, attrs } = useUI('meter', toRef(props, 'ui'), config, toRef(props, 'class')) const { ui, attrs } = useUI('meter', toRef(props, 'ui'), config, toRef(props, 'class'))

View File

@@ -3,7 +3,7 @@ import type { ComputedRef, VNode, SlotsType, PropType } from 'vue'
import { twJoin } from 'tailwind-merge' import { twJoin } from 'tailwind-merge'
import UIcon from '../elements/Icon.vue' import UIcon from '../elements/Icon.vue'
import { useUI } from '../../composables/useUI' import { useUI } from '../../composables/useUI'
import { getSlotsChildren, mergeConfig } from '../../utils' import { mergeConfig, getSlotsChildren } from '../../utils'
import type { DeepPartial, Strategy, MeterSize } from '../../types/index' import type { DeepPartial, Strategy, MeterSize } from '../../types/index'
import type Meter from './Meter.vue' import type Meter from './Meter.vue'
// @ts-expect-error // @ts-expect-error

View File

@@ -32,10 +32,10 @@
<script lang="ts"> <script lang="ts">
import { computed, toRef, defineComponent } from 'vue' import { computed, toRef, defineComponent } from 'vue'
import type { PropType } from 'vue' import type { PropType } from 'vue'
import { twJoin } from 'tailwind-merge' import { twMerge, twJoin } from 'tailwind-merge'
import { useUI } from '../../composables/useUI' import { useUI } from '../../composables/useUI'
import { useFormGroup } from '../../composables/useFormGroup' import { useFormGroup } from '../../composables/useFormGroup'
import { mergeConfig, twMerge } from '../../utils' import { mergeConfig } from '../../utils'
import type { DeepPartial, Strategy } from '../../types/index' import type { DeepPartial, Strategy } from '../../types/index'
// @ts-expect-error // @ts-expect-error
import appConfig from '#build/app.config' import appConfig from '#build/app.config'

View File

@@ -5,12 +5,14 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { provide, ref, defineComponent, onUnmounted, onMounted, readonly } from 'vue' import { provide, ref, type PropType, defineComponent, onUnmounted, onMounted, readonly } from 'vue'
import type { PropType } from 'vue'
import { useEventBus } from '@vueuse/core' import { useEventBus } from '@vueuse/core'
import type { ZodSchema } from 'zod'
import type { ValidationError as JoiError, Schema as JoiSchema } from 'joi' import type { ValidationError as JoiError, Schema as JoiSchema } from 'joi'
import type { ObjectSchema as YupObjectSchema, ValidationError as YupError } from 'yup' import type { ObjectSchema as YupObjectSchema, ValidationError as YupError } from 'yup'
import type { StandardSchemaV1 } from '@standard-schema/spec' import type { BaseSchema as ValibotSchema30, BaseSchemaAsync as ValibotSchemaAsync30 } from 'valibot30'
import type { GenericSchema as ValibotSchema31, GenericSchemaAsync as ValibotSchemaAsync31, SafeParser as ValibotSafeParser31, SafeParserAsync as ValibotSafeParserAsync31 } from 'valibot31'
import type { GenericSchema as ValibotSchema, GenericSchemaAsync as ValibotSchemaAsync, SafeParser as ValibotSafeParser, SafeParserAsync as ValibotSafeParserAsync } from 'valibot'
import type { Struct } from 'superstruct' import type { Struct } from 'superstruct'
import type { FormError, FormEvent, FormEventType, FormSubmitEvent, FormErrorEvent, Form, ValidateReturnSchema } from '../../types/form' import type { FormError, FormEvent, FormEventType, FormSubmitEvent, FormErrorEvent, Form, ValidateReturnSchema } from '../../types/form'
import { useId } from '#imports' import { useId } from '#imports'
@@ -23,10 +25,14 @@ class FormException extends Error {
} }
} }
type Schema = PropType<StandardSchemaV1> type Schema = PropType<ZodSchema>
| PropType<YupObjectSchema<any>> | PropType<YupObjectSchema<any>>
| PropType<Struct<any, any>>
| PropType<JoiSchema> | PropType<JoiSchema>
| PropType<ValibotSchema30 | ValibotSchemaAsync30>
| PropType<ValibotSchema31 | ValibotSchemaAsync31>
| PropType<ValibotSafeParser31<any, any> | ValibotSafeParserAsync31<any, any>>
| PropType<ValibotSchema | ValibotSchemaAsync>
| PropType<ValibotSafeParser<any, any> | ValibotSafeParserAsync<any, any>> | PropType<Struct<any, any>>
export default defineComponent({ export default defineComponent({
props: { props: {
@@ -54,8 +60,6 @@ export default defineComponent({
const formId = useId() const formId = useId()
const bus = useEventBus<FormEvent>(`form-${formId}`) const bus = useEventBus<FormEvent>(`form-${formId}`)
const parsedValue = ref(null)
onMounted(() => { onMounted(() => {
bus.on(async (event) => { bus.on(async (event) => {
if (event.type !== 'submit' && props.validateOn?.includes(event.type)) { if (event.type !== 'submit' && props.validateOn?.includes(event.type)) {
@@ -83,7 +87,7 @@ export default defineComponent({
if (errors) { if (errors) {
errs = errs.concat(errors) errs = errs.concat(errors)
} else { } else {
parsedValue.value = result Object.assign(props.state, result)
} }
} }
@@ -126,7 +130,7 @@ export default defineComponent({
if (props.validateOn?.includes('submit')) { if (props.validateOn?.includes('submit')) {
await validate() await validate()
} }
event.data = props.schema ? parsedValue.value : props.state event.data = props.state
emit('submit', event) emit('submit', event)
} catch (error) { } catch (error) {
if (!(error instanceof FormException)) { if (!(error instanceof FormException)) {
@@ -206,18 +210,26 @@ function isJoiError(error: any): error is JoiError {
return error.isJoi === true return error.isJoi === true
} }
export function isStandardSchema(schema: any): schema is StandardSchemaV1 { function isValibotSchema(schema: any): schema is ValibotSchema30 | ValibotSchemaAsync30 | ValibotSchema31 | ValibotSchemaAsync31 | ValibotSafeParser31<any, any> | ValibotSafeParserAsync31<any, any> | ValibotSchema | ValibotSchemaAsync | ValibotSafeParser<any, any> | ValibotSafeParserAsync<any, any> {
return '~standard' in schema return '_parse' in schema || '_run' in schema || (typeof schema === 'function' && 'schema' in schema)
} }
export async function validateStandardSchema( function isZodSchema(schema: any): schema is ZodSchema {
return schema.parse !== undefined
}
async function validateValibotSchema(
state: any, state: any,
schema: StandardSchemaV1 schema: ValibotSchema30 | ValibotSchemaAsync30 | ValibotSchema31 | ValibotSchemaAsync31 | ValibotSafeParser31<any, any> | ValibotSafeParserAsync31<any, any> | ValibotSchema | ValibotSchemaAsync | ValibotSafeParser<any, any> | ValibotSafeParserAsync<any, any>
): Promise<ValidateReturnSchema<typeof state>> { ): Promise<ValidateReturnSchema<typeof state>> {
const result = await schema['~standard'].validate(state) const result = await ('_parse' in schema ? schema._parse(state) : '_run' in schema ? schema._run({ typed: false, value: state }, {}) : schema(state))
if (!result.issues || result.issues.length === 0) { if (!result.issues || result.issues.length === 0) {
const output = ('value' in result ? result.value : null) const output = ('output' in result
? result.output
: 'value' in result
? result.value
: null)
return { return {
errors: null, errors: null,
result: output result: output
@@ -225,7 +237,7 @@ export async function validateStandardSchema(
} }
const errors = result.issues.map(issue => ({ const errors = result.issues.map(issue => ({
path: issue.path?.map(item => typeof item === 'object' ? item.key : item).join('.') || '', path: issue.path?.map(item => item.key).join('.') || '',
message: issue.message message: issue.message
})) }))
@@ -262,6 +274,28 @@ async function validateJoiSchema(
} }
} }
async function validateZodSchema(
state: any,
schema: ZodSchema
): Promise<ValidateReturnSchema<typeof state>> {
const result = await schema.safeParseAsync(state)
if (result.success === false) {
const errors = result.error.issues.map(issue => ({
path: issue.path.join('.'),
message: issue.message
}))
return {
errors,
result: null
}
}
return {
result: result.data,
errors: null
}
}
async function validateSuperstructSchema(state: any, schema: Struct<any, any>): Promise<ValidateReturnSchema<typeof state>> { async function validateSuperstructSchema(state: any, schema: Struct<any, any>): Promise<ValidateReturnSchema<typeof state>> {
const [err, result] = schema.validate(state) const [err, result] = schema.validate(state)
if (err) { if (err) {
@@ -287,7 +321,7 @@ async function validateYupSchema(
schema: YupObjectSchema<any> schema: YupObjectSchema<any>
): Promise<ValidateReturnSchema<typeof state>> { ): Promise<ValidateReturnSchema<typeof state>> {
try { try {
const result = await schema.validate(state, { abortEarly: false }) const result = schema.validateSync(state, { abortEarly: false })
return { return {
errors: null, errors: null,
result result
@@ -310,10 +344,12 @@ async function validateYupSchema(
} }
function parseSchema(state: any, schema: Schema): Promise<ValidateReturnSchema<typeof state>> { function parseSchema(state: any, schema: Schema): Promise<ValidateReturnSchema<typeof state>> {
if (isStandardSchema(schema)) { if (isZodSchema(schema)) {
return validateStandardSchema(state, schema) return validateZodSchema(state, schema)
} else if (isJoiSchema(schema)) { } else if (isJoiSchema(schema)) {
return validateJoiSchema(state, schema) return validateJoiSchema(state, schema)
} else if (isValibotSchema(schema)) {
return validateValibotSchema(state, schema)
} else if (isYupSchema(schema)) { } else if (isYupSchema(schema)) {
return validateYupSchema(state, schema) return validateYupSchema(state, schema)
} else if (isSuperStructSchema(schema)) { } else if (isSuperStructSchema(schema)) {

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