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
attributes:
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
id: env
attributes:

View File

@@ -5,8 +5,8 @@ body:
- type: markdown
attributes:
value: |
> [!IMPORTANT]
> As Nuxt UI v3 is currently in alpha, we recommend thorough testing before using it in production environments. We're actively working on stabilization and welcome feedback from early adopters to improve the library.
> [!IMPORTANT]
> As Nuxt UI v3 is currently in alpha, we recommend thorough testing before using it in production environments. We're actively working on stabilization and welcome feedback from early adopters to improve the library.
- type: markdown
attributes:
value: |
@@ -29,20 +29,11 @@ body:
- Build Modules: `-`
validations:
required: true
- type: dropdown
id: package
attributes:
label: Is this bug related to Nuxt or Vue?
options:
- Nuxt
- Vue
validations:
required: true
- type: input
id: version
attributes:
label: Version
placeholder: v3.0.0-alpha.x
placeholder: v3.0.0-alpha.5
validations:
required: true
- type: textarea

View File

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

View File

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

View File

@@ -1,15 +1,15 @@
name: module
name: ci-dev
on:
push:
branches:
- v2
- dev
pull_request:
branches:
- v2
- dev
jobs:
build:
ci:
runs-on: ${{ matrix.os }}
permissions:
@@ -19,7 +19,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest] # macos-latest, windows-latest
node: [22]
node: [20]
env:
NUXT_GITHUB_TOKEN: ${{ secrets.NUXT_GITHUB_TOKEN }}
@@ -37,6 +37,16 @@ jobs:
node-version: ${{ matrix.node }}
cache: pnpm
- name: Filter changes
uses: dorny/paths-filter@v3
id: changes
with:
filters: |
src:
- 'src/**'
- 'package.json'
- 'pnpm-lock.yaml'
- name: Install dependencies
run: pnpm install
@@ -55,5 +65,8 @@ jobs:
- name: Test
run: pnpm run test run
- name: Publish
run: pnpx pkg-pr-new publish --compact --no-template --pnpm
- name: Release Edge
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:
push:
tags:
- 'v2*'
branches:
- main
jobs:
publish:
ci:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest] # macos-latest, windows-latest
node: [22]
node: [20]
env:
NUXT_GITHUB_TOKEN: ${{ secrets.NUXT_GITHUB_TOKEN }}
@@ -48,7 +48,14 @@ jobs:
- name: Test
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
env:
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
## [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)
### 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
@@ -22,32 +22,27 @@ Its goal is to provide everything related to UI when building a Nuxt app. This i
- Fully typed
- [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
```bash
# npm
npm install @nuxt/ui@2
# yarn
yarn add @nuxt/ui@2
# pnpm
pnpm add @nuxt/ui@2
# bun
bun add @nuxt/ui@2
npx nuxi@latest module add ui
```
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
export default defineNuxtConfig({
modules: ['@nuxt/ui']
})
```json
{
"devDependencies": {
"@nuxt/ui": "npm:@nuxt/ui-edge@latest"
}
}
```
## Documentation
Visit https://ui2.nuxt.com to explore the documentation.
Visit https://ui.nuxt.com to explore the documentation.
## 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:
- 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
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
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 -->
[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: [
{ 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: {
lang: 'en'

View File

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

View File

@@ -10,34 +10,12 @@
}"
>
<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" />
<Logo v-else class="w-auto h-6 shrink-0" />
</NuxtLink>
<UDropdown
: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' }]]"
: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>
<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" />
</NuxtLink>
</template>
<template #right>
@@ -47,10 +25,10 @@
<UContentSearchButton :label="null" />
</UTooltip>
<UColorModeButton class="hidden lg:inline-flex" />
<UColorModeButton />
<UButton
to="https://github.com/nuxt/ui/tree/v2"
to="https://github.com/nuxt/ui"
target="_blank"
icon="i-simple-icons-github"
aria-label="GitHub"

View File

@@ -9,7 +9,7 @@ onMounted(() => {
if (carbonads.value) {
const script = document.createElement('script')
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')
carbonads.value.appendChild(script)
}

View File

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

View File

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

View File

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

View File

@@ -21,7 +21,7 @@ async function onSubmit(event: FormSubmitEvent<Schema>) {
</script>
<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">
<UInput v-model="state.email" />
</UFormGroup>

View File

@@ -1,6 +1,5 @@
<script setup lang="ts">
import { object, string } from 'yup'
import type { InferType } from 'yup'
import { object, string, type InferType } from 'yup'
import type { FormSubmitEvent } from '#ui/types'
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>
<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">
<RangeExample />
</div>

View File

@@ -7,29 +7,13 @@ description: 'Learn how to install and configure Nuxt UI in your application.'
### 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 [pnpm]
pnpm add @nuxt/ui@2
```bash
npx nuxi@latest module add ui
```
```bash [yarn]
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`:
2. Add it to the `modules` section in your `nuxt.config.ts`:
```ts [nuxt.config.ts]
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.
```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.
@@ -110,7 +94,7 @@ You can read more about this in the [Theming](/getting-started/theming#dark-mode
## 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"}
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. |
| `global` | `false` | Expose components globally. |
| `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:
@@ -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]
{
"dependencies": {
- "@nuxt/ui": "^2.21.0",
+ "@nuxt/ui": "https://pkg.pr.new/@nuxt/ui@bf1c9e7",
"devDependencies": {
- "@nuxt/ui": "^2.11.0"
+ "@nuxt/ui": "npm:@nuxt/ui-edge@latest"
}
}
```
::note
**pkg.pr.new** will automatically comment on PRs with the installation URL, making it easy to test changes.
::
Then run `pnpm install`, `yarn install` or `npm install`.

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`.
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]
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.
@@ -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
All the components are styled with dark mode in mind.
@@ -389,12 +343,6 @@ export default defineAppConfig({
loadingIcon: 'i-octicon-sync-24'
}
},
inputMenu: {
default: {
selectedIcon: 'i-octicon-check-24',
trailingIcon: 'i-octicon-chevron-down-24'
}
},
select: {
default: {
loadingIcon: 'i-octicon-sync-24',
@@ -430,9 +378,6 @@ export default defineAppConfig({
sortButton: {
icon: 'i-octicon-arrow-switch-24'
},
expandButton: {
icon: 'i-octicon-chevron-down-24'
},
loadingState: {
icon: 'i-octicon-sync-24'
},
@@ -466,21 +411,6 @@ export default defineAppConfig({
default: {
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.
Modifiers are also available:
| Modifier | Description |
|----------|-------------|
| `meta` | Acts as `Command (⌘)` on macOS and `Control (Ctrl)` on Windows/Linux. |
| `ctrl` | Represents the `Control (Ctrl)` key across all operating systems. |
| `shift` | Represents the `Shift` key, only needed for alphabetic keys (e.g., `shift_e`). |
- `meta`: acts as `Command` for MacOS and `Control` for others
- `ctrl`: acts as `Control`
- `shift`: acts as `Shift` and is only necessary for alphabetic keys
Examples of keys:
| Shortcut Key | Action |
|---------------|--------|
| `escape` | Triggers when `Esc` is pressed |
| `meta_k` | `⌘ + K` on Mac, `Ctrl + K` on Windows/Linux |
| `ctrl_k` | Triggers `Ctrl + K` on all OS |
| `shift_e` | Triggers `Shift + E` on all OS |
| `?` | Triggers `?` (Shift + `/` on US Mac keyboards) |
| `g-d` | Triggers when `g` then `d` are pressed within 800ms |
| `arrowleft` | Triggers when `←` is pressed (also: `arrowright`, `arrowup`, `arrowdown`) |
- `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
- `ctrl_k`: will trigger by hitting `Ctrl` and `K` at the same time on MacOS, Windows and Linux
- `shift_e`: will trigger by hitting `Shift` and `E` at the same time on MacOS, Windows and Linux
- `?`: will trigger by hitting `?` on some keyboard layouts, or for example `Shift` and `/`, which results in `?` on US Mac keyboards
- `g-d`: will trigger by hitting `g` then `d` with a maximum delay of 800ms by default
- `arrowleft`: will trigger by hitting `` (also: `arrowright`, `arrowup`, `arrowdown`)
::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.

View File

@@ -6,7 +6,7 @@ links:
to: 'https://headlessui.com/v1/vue/disclosure'
- label: 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

View File

@@ -3,7 +3,7 @@ description: Display an alert element to draw attention.
links:
- label: 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

View File

@@ -3,7 +3,7 @@ description: Display an image that represents a resource or a group of resources
links:
- label: 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

View File

@@ -3,7 +3,7 @@ description: Display a short text to represent a status or a category.
links:
- label: 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
@@ -141,74 +141,6 @@ Badge
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
:component-props

View File

@@ -4,7 +4,7 @@ description: A list of links that indicate the current page's location within a
links:
- label: 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

View File

@@ -3,7 +3,7 @@ description: Create a button with icon or link capabilities.
links:
- label: 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

View File

@@ -3,7 +3,7 @@ description: Display a card for content with a header, body and footer.
links:
- label: 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

View File

@@ -3,7 +3,7 @@ description: Display images or content in a scrollable area.
links:
- label: 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

View File

@@ -3,7 +3,7 @@ description: Display a checkbox field.
links:
- label: 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

View File

@@ -3,7 +3,7 @@ description: Display a chip indicator on any component.
links:
- label: 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

View File

@@ -7,7 +7,7 @@ links:
to: 'https://headlessui.com/v1/vue/combobox'
- label: 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
@@ -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!
::
@@ -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!
::

View File

@@ -3,7 +3,7 @@ description: A container lets you center and constrain the width of your content
links:
- label: 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

View File

@@ -4,7 +4,7 @@ description: Display a menu that appears on right click.
links:
- label: 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

View File

@@ -3,7 +3,7 @@ description: Display a separator between content.
links:
- label: 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

View File

@@ -6,7 +6,7 @@ links:
to: https://headlessui.com/v1/vue/menu
- label: 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

View File

@@ -4,7 +4,7 @@ description: Display a label and additional informations around a form element.
links:
- label: 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:
- label: 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
@@ -14,7 +14,7 @@ It works with the [FormGroup](/components/form-group) component to display error
The form component requires two props:
- `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"}
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!
::

View File

@@ -4,7 +4,7 @@ description: Display a list of horizontal links.
links:
- label: 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

View File

@@ -7,7 +7,7 @@ links:
to: 'https://headlessui.com/v1/vue/combobox'
- label: 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

View File

@@ -3,7 +3,7 @@ description: Display an input field.
links:
- label: 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
@@ -142,7 +142,7 @@ props:
### 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`.
@@ -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
### `leading`

View File

@@ -4,7 +4,7 @@ description: Display a keyboard key in a text block.
links:
- label: 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:
title: 'Kbd'
---

View File

@@ -4,7 +4,7 @@ description: Render a NuxtLink but with superpowers.
links:
- label: 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
@@ -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.
- `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.
- 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.

View File

@@ -4,7 +4,7 @@ description: Display a gauge meter that fills or depletes.
links:
- label: 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

View File

@@ -6,7 +6,7 @@ links:
to: 'https://headlessui.com/v1/vue/dialog'
- label: 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

View File

@@ -3,7 +3,7 @@ description: Display a toast notification in your app.
links:
- label: 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
@@ -137,9 +137,9 @@ excludedProps:
### 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
---
@@ -149,7 +149,6 @@ baseProps:
description: 'This is a notification.'
props:
timeout: 60000
pauseTimeoutOnHover: true
---
::

View File

@@ -3,7 +3,7 @@ description: Add a pagination to handle pages.
links:
- label: 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

View File

@@ -6,7 +6,7 @@ links:
to: 'https://headlessui.com/v1/vue/popover'
- label: 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

View File

@@ -4,7 +4,7 @@ description: Show a horizontal bar to indicate task progression.
links:
- label: 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

View File

@@ -4,7 +4,7 @@ description: Display a set of radio buttons.
links:
- label: 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

View File

@@ -3,7 +3,7 @@ description: Display a range field
links:
- label: 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

View File

@@ -7,7 +7,7 @@ links:
to: 'https://headlessui.com/v1/vue/listbox'
- label: 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
@@ -156,7 +156,38 @@ Use the `searchableLazy` prop to control the immediacy of data requests.
---
component: 'select-menu-example-search-async'
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:
- label: 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

View File

@@ -3,7 +3,7 @@ description: Display a placeholder while content is loading.
links:
- label: 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

View File

@@ -6,7 +6,7 @@ links:
to: 'https://headlessui.com/v1/vue/dialog'
- label: 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

View File

@@ -3,7 +3,7 @@ description: 'Display data in a table.'
links:
- label: 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
@@ -62,7 +62,7 @@ extraClass: 'overflow-hidden'
padding: false
component: 'table-example-columns-selectable'
componentProps:
class: 'flex-1 flex-col overflow-hidden min-h-[230px]'
class: 'flex-1 flex-col overflow-hidden'
---
::
@@ -311,9 +311,9 @@ const onUpdateSelection = (selectedRows: any[]) => {
</script>
<template>
<UTable
v-model="selected"
:rows="people"
<UTable
v-model="selected"
:rows="people"
@select:all="onHandleSelectAll"
@update:modelValue="onUpdateSelection"
/>
@@ -346,7 +346,7 @@ componentProps:
### 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.
@@ -598,7 +598,7 @@ This slot allows you to customize the checkbox appearance in the table header fo
#### Usage
```vue
<template>
<UTable v-model="selectable">
<UTable v-model="selectable">
<template #select-header="{ checked, change, indeterminate }">
<!-- Place your custom component here -->
</template>
@@ -620,7 +620,7 @@ This slot allows you to customize the checkbox appearance in the table header fo
<UTable>
<!-- Header checkbox customization -->
<template #select-header="{ indeterminate, checked, change }">
<input
<input
type="checkbox"
:indeterminate="indeterminate"
:checked="checked"
@@ -658,7 +658,7 @@ This slot allows you to customize the checkbox appearance for each row in the ta
<UTable>
<!-- Row checkbox customization -->
<template #select-data="{ checked, change }">
<input
<input
type="checkbox"
:checked="checked"
@change="e => change(e.target.checked)"
@@ -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!
::

View File

@@ -3,7 +3,7 @@ description: A set of tab panels that are displayed one at a time.
links:
- label: 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

View File

@@ -3,7 +3,7 @@ description: Display a textarea field.
links:
- label: 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

View File

@@ -6,7 +6,7 @@ links:
to: 'https://headlessui.com/v1/vue/switch'
- label: 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

View File

@@ -3,7 +3,7 @@ description: Display content that appears on hover next to an element.
links:
- label: 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

View File

@@ -4,7 +4,7 @@ description: Display a list of vertical links.
links:
- label: 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

View File

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

View File

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

View File

@@ -15,7 +15,7 @@ export default defineNuxtConfig({
]
: [
'@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),
modules: [
@@ -26,11 +26,13 @@ export default defineNuxtConfig({
module,
'@nuxtjs/plausible',
'@vueuse/nuxt',
'nuxt-component-meta'
'nuxt-component-meta',
'nuxt-cloudflare-analytics',
'modules/content-examples-code'
],
site: {
url: 'https://ui2.nuxt.com'
url: 'https://ui.nuxt.com'
},
content: {
@@ -52,7 +54,7 @@ export default defineNuxtConfig({
prefix: '/pro',
driver: 'github',
repo: 'nuxt/ui-pro',
branch: 'v1',
branch: 'dev',
dir: '.docs/content/pro',
token: process.env.NUXT_GITHUB_TOKEN || ''
}
@@ -93,11 +95,6 @@ export default defineNuxtConfig({
vite: {
optimizeDeps: {
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: {
exclude: [
'@nuxt/content',

View File

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

View File

@@ -78,7 +78,7 @@ defineOgImageComponent('Docs', {
const communityLinks = computed(() => [{
icon: 'i-heroicons-pencil-square',
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'
}, {
icon: 'i-heroicons-star',

View File

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

View File

@@ -41,8 +41,8 @@ if (!page.value) {
throw createError({ statusCode: 404, statusMessage: 'Page not found', fatal: true })
}
const { data: releases } = await useFetch('/api/releases.json', { key: 'releases-list' })
const { data: pulls } = await useLazyFetch('/api/pulls.json', { default: () => [], key: 'pulls-list' })
const { data: releases } = await useFetch('/api/releases.json')
const { data: pulls } = await useLazyFetch('/api/pulls.json', { default: () => [] })
const dates = computed(() => {
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",
"description": "A UI Library for Modern Web Apps, powered by Vue & Tailwind CSS.",
"version": "2.22.1",
"packageManager": "pnpm@10.13.1",
"repository": {
"type": "git",
"url": "git+https://github.com/nuxt/ui.git"
},
"homepage": "https://ui2.nuxt.com",
"version": "2.19.2",
"packageManager": "pnpm@9.12.3",
"repository": "nuxt/ui",
"homepage": "https://ui.nuxt.com",
"type": "module",
"license": "MIT",
"exports": {
".": {
"types": "./dist/module.d.mts",
"import": "./dist/module.mjs"
"import": "./dist/module.mjs",
"require": "./dist/module.cjs"
}
},
"typesVersions": {
"*": {
".": [
"./dist/module.d.mts"
]
}
},
"main": "./dist/module.mjs",
"main": "./dist/module.cjs",
"types": "./dist/types.d.ts",
"files": [
"dist"
],
@@ -41,78 +32,56 @@
"test": "vitest"
},
"dependencies": {
"@headlessui/tailwindcss": "^0.2.2",
"@headlessui/tailwindcss": "^0.2.1",
"@headlessui/vue": "^1.7.23",
"@iconify-json/heroicons": "^1.2.2",
"@nuxt/icon": "^1.15.0",
"@nuxt/kit": "^4.0.1",
"@iconify-json/heroicons": "^1.2.1",
"@nuxt/icon": "^1.7.2",
"@nuxt/kit": "^3.14.159",
"@nuxtjs/color-mode": "^3.5.2",
"@nuxtjs/tailwindcss": "^6.14.0",
"@nuxtjs/tailwindcss": "^6.12.2",
"@popperjs/core": "^2.11.8",
"@standard-schema/spec": "^1.0.0",
"@tailwindcss/aspect-ratio": "^0.4.2",
"@tailwindcss/container-queries": "^0.1.1",
"@tailwindcss/forms": "^0.5.10",
"@tailwindcss/typography": "^0.5.16",
"@vueuse/core": "^13.5.0",
"@vueuse/integrations": "^13.5.0",
"@vueuse/math": "^13.5.0",
"@tailwindcss/forms": "^0.5.9",
"@tailwindcss/typography": "^0.5.15",
"@vueuse/core": "^11.2.0",
"@vueuse/integrations": "^11.2.0",
"@vueuse/math": "^11.2.0",
"defu": "^6.1.4",
"fuse.js": "^7.1.0",
"ohash": "^2.0.11",
"pathe": "^2.0.3",
"fuse.js": "^7.0.0",
"ohash": "^1.1.4",
"pathe": "^1.1.2",
"scule": "^1.3.0",
"tailwind-merge": "^2.6.0",
"tailwindcss": "^3.4.17"
"tailwind-merge": "^2.5.4",
"tailwindcss": "^3.4.14"
},
"devDependencies": {
"@nuxt/eslint-config": "^1.6.0",
"@nuxt/module-builder": "^1.0.1",
"@nuxt/test-utils": "^3.19.2",
"@release-it/conventional-changelog": "^10.0.1",
"@nuxt/eslint-config": "^0.6.1",
"@nuxt/module-builder": "^0.8.4",
"@nuxt/test-utils": "^3.14.4",
"@release-it/conventional-changelog": "^9.0.2",
"@vue/test-utils": "^2.4.6",
"eslint": "^9.31.0",
"happy-dom": "^18.0.1",
"nuxt": "^4.0.1",
"release-it": "^19.0.4",
"typescript": "^5.8.3",
"vitest": "^3.2.4",
"eslint": "^9.14.0",
"happy-dom": "^14.12.3",
"joi": "^17.13.3",
"nuxt": "^3.14.159",
"release-it": "^17.10.0",
"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",
"vue-tsc": "^3.0.3"
},
"peerDependencies": {
"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
}
"vue-tsc": "^2.1.10",
"yup": "^1.4.0",
"zod": "^3.23.8"
},
"resolutions": {
"@nuxt/ui": "workspace:*",
"@nuxt/content": "2.13.2",
"@nuxtjs/mdc": "0.9.0",
"@vueuse/core": "^13.5.0",
"@vueuse/integrations": "^13.5.0",
"@vueuse/math": "^13.5.0",
"@vueuse/nuxt": "13.1.0",
"unimport": "4.1.1",
"chokidar": "3.6.0"
"nuxt": "3.13.2",
"@nuxt/kit": "3.13.2"
}
}

View File

@@ -9,6 +9,6 @@
},
"dependencies": {
"@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:
- ./
- docs
- playground
ignoredBuiltDependencies:
- '@parcel/watcher'
- esbuild
- sharp
- vue-demi
- "./"
- "docs"
- "playground"

View File

@@ -5,7 +5,12 @@
"lockFileMaintenance": {
"enabled": true
},
"baseBranches": ["v2", "v3"],
"ignoreDeps": [
"happy-dom",
"valibot30",
"valibot31"
],
"baseBranches": ["dev", "v3"],
"packageRules": [{
"matchBaseBranches": ["v3"],
"labels": ["v3"]
@@ -19,6 +24,5 @@
}, {
"matchDepTypes": ["resolutions"],
"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
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 { defineNuxtModule, installModule, addComponentsDir, addImportsDir, createResolver, addPlugin } from '@nuxt/kit'
import type { ConfigExtension, DefaultClassGroupIds, DefaultThemeGroupIds } from 'tailwind-merge'
import { name, version } from '../package.json'
import createTemplates from './templates'
import type * as config from './runtime/ui.config'
@@ -21,7 +20,6 @@ type UI = {
gray?: string
colors?: string[]
strategy?: Strategy
tailwindMerge?: ConfigExtension<DefaultClassGroupIds, DefaultThemeGroupIds>
[key: string]: any
} & DeepPartial<typeof config, string | number | boolean>
@@ -43,11 +41,6 @@ export interface ModuleOptions {
*/
global?: boolean
/**
* @default true
*/
colorMode?: boolean
safelistColors?: string[]
/**
* Disables the global css styles added by the module.
@@ -66,7 +59,6 @@ export default defineNuxtModule<ModuleOptions>({
},
defaults: {
prefix: 'U',
colorMode: true,
safelistColors: ['primary'],
disableGlobalStyles: false
},
@@ -89,9 +81,7 @@ export default defineNuxtModule<ModuleOptions>({
// Modules
await installModule('@nuxt/icon')
if (options.colorMode) {
await installModule('@nuxtjs/color-mode', { classSuffix: '' })
}
await installModule('@nuxtjs/color-mode', { classSuffix: '' })
await installTailwind(options, nuxt, resolve)
// 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]"
: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
:model-value="isAllRowChecked"
:indeterminate="indeterminate"
@@ -93,19 +93,19 @@
/>
</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]">
<!-- 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 -->
<span v-if="modelValue && column.key === 'select' " @click.capture.stop="() => {}">
<slot name="select-data" :checked="isSelected(row)" :change="(ev: boolean) => onChangeCheckbox(ev, row)">
<UCheckbox
:model-value="isSelected(row)"
v-bind="ui.default.checkbox"
aria-label="Select row"
@change="onChangeCheckbox($event, row)"
/>
</slot>
</span>
<slot v-if="modelValue && (column.key === 'select' || shouldRenderColumnInFirstPlace(subIndex, 'select')) " name="select-data" :checked="isSelected(row)" :change="(ev: boolean) => onChangeCheckbox(ev, row)">
<UCheckbox
:model-value="isSelected(row)"
v-bind="ui.default.checkbox"
aria-label="Select row"
@change="onChangeCheckbox($event, row)"
@click.capture.stop="() => onSelect(row)"
/>
</slot>
<slot
v-else
:key="retriggerSlot"
:name="`${column.key}-data`"
:column="column"
:row="row"
@@ -133,18 +133,18 @@
</template>
<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 { upperFirst } from 'scule'
import { defu } from 'defu'
import { useVModel } from '@vueuse/core'
import { isEqual } from 'ohash/utils'
import { isEqual } from 'ohash'
import UIcon from '../elements/Icon.vue'
import UButton from '../elements/Button.vue'
import UProgress from '../elements/Progress.vue'
import UCheckbox from '../forms/Checkbox.vue'
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'
// @ts-expect-error
import appConfig from '#build/app.config'
@@ -274,30 +274,7 @@ export default defineComponent({
setup(props, { emit, attrs: $attrs }) {
const { ui, attrs } = useUI('table', toRef(props, 'ui'), config, toRef(props, 'class'))
const columns = computed(() => {
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 columns = computed(() => props.columns ?? Object.keys(props.rows[0] ?? {}).map(key => ({ key, label: upperFirst(key), sortable: false, class: undefined, sort: defaultSort }) as TableColumn))
const sort = useVModel(props, 'sort', emit, { passive: true, defaultValue: defu({}, props.sort, { column: null, direction: 'asc' }) })
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 rows = computed(() => {
@@ -443,7 +422,8 @@ export default defineComponent({
if (checked) {
selected.value = props.singleSelect ? [row] : [...selected.value, row]
} 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
}
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) {
expand.value = {
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
}
watch(rows, () => {
retriggerSlot.value = new Date()
}, {
deep: true
})
return {
// eslint-disable-next-line vue/no-dupe-keys
ui,
@@ -508,7 +501,9 @@ export default defineComponent({
getRowData,
toggleOpened,
getAriaSort,
isExpanded
isExpanded,
shouldRenderColumnInFirstPlace,
retriggerSlot
}
}
})

View File

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

View File

@@ -14,7 +14,7 @@
{{ title }}
</slot>
</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">
{{ description }}
</slot>
@@ -42,13 +42,13 @@
<script lang="ts">
import { computed, toRef, defineComponent } 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 UAvatar from '../elements/Avatar.vue'
import UButton from '../elements/Button.vue'
import { useUI } from '../../composables/useUI'
import type { Avatar, Button, AlertColor, AlertVariant, AlertAction, Strategy, DeepPartial } from '../../types/index'
import { mergeConfig, twMerge } from '../../utils'
import { mergeConfig } from '../../utils'
// @ts-expect-error
import appConfig from '#build/app.config'
import { alert } from '#ui/ui.config'

View File

@@ -23,10 +23,10 @@
<script lang="ts">
import { defineComponent, ref, computed, toRef, watch } 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 { mergeConfig, twMerge } from '../../utils'
import { mergeConfig } from '../../utils'
import type { AvatarSize, AvatarChipColor, AvatarChipPosition, Strategy, DeepPartial } from '../../types/index'
// @ts-expect-error
import appConfig from '#build/app.config'

View File

@@ -1,8 +1,8 @@
import { h, cloneVNode, computed, toRef, defineComponent } from 'vue'
import type { PropType } from 'vue'
import { twJoin } from 'tailwind-merge'
import { twMerge, twJoin } from 'tailwind-merge'
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 UAvatar from './Avatar.vue'
// @ts-expect-error

View File

@@ -1,28 +1,15 @@
<template>
<span :class="badgeClass" v-bind="attrs">
<slot name="leading">
<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>
<slot>{{ label }}</slot>
</span>
</template>
<script lang="ts">
import { computed, toRef, defineComponent } from 'vue'
import type { PropType } from 'vue'
import { twJoin } from 'tailwind-merge'
import UIcon from '../elements/Icon.vue'
import { twMerge, twJoin } from 'tailwind-merge'
import { useUI } from '../../composables/useUI'
import { mergeConfig, twMerge } from '../../utils'
import { mergeConfig } from '../../utils'
import { useInjectButtonGroup } from '../../composables/useButtonGroup'
import type { BadgeColor, BadgeSize, BadgeVariant, DeepPartial, Strategy } from '../../types/index'
// @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)
export default defineComponent({
components: {
UIcon
},
inheritAttrs: false,
props: {
size: {
@@ -65,26 +49,6 @@ export default defineComponent({
type: [String, Number],
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: {
type: [String, Object, Array] as PropType<any>,
default: () => ''
@@ -99,14 +63,6 @@ export default defineComponent({
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 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,
rounded.value,
ui.value.size[size.value],
ui.value.gap[size.value],
variant?.replaceAll('{color}', props.color)
), 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 {
attrs,
isLeading,
isTrailing,
badgeClass,
leadingIconName,
trailingIconName,
leadingIconClass,
trailingIconClass
badgeClass
}
}
})

View File

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

View File

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

View File

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

View File

@@ -60,13 +60,13 @@ import { defineComponent, ref, computed, watch, toRef, onMounted, resolveCompone
import type { PropType } from 'vue'
import { Menu as HMenu, MenuButton as HMenuButton, MenuItems as HMenuItems, MenuItem as HMenuItem, provideUseId } from '@headlessui/vue'
import { defu } from 'defu'
import { twJoin } from 'tailwind-merge'
import { twMerge, twJoin } from 'tailwind-merge'
import UIcon from '../elements/Icon.vue'
import UAvatar from '../elements/Avatar.vue'
import UKbd from '../elements/Kbd.vue'
import { useUI } from '../../composables/useUI'
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'
// @ts-expect-error
import appConfig from '#build/app.config'

View File

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

View File

@@ -32,9 +32,8 @@
</template>
<script lang="ts">
import { isEqual, diff } from 'ohash/utils'
import { isEqual } from 'ohash'
import { defineComponent } from 'vue'
import type { PropType } from 'vue'
import { nuxtLinkProps } from '../../utils'
export default defineComponent({
@@ -62,7 +61,7 @@ export default defineComponent({
default: false
},
exactQuery: {
type: [Boolean, String] as PropType<boolean | 'partial'>,
type: Boolean,
default: false
},
exactHash: {
@@ -75,25 +74,9 @@ export default defineComponent({
}
},
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 }) {
if (props.exactQuery === 'partial') {
if (!isPartiallyEqual(route.query, $route.query)) return props.inactiveClass
} else if (props.exactQuery === true) {
if (!isEqual(route.query, $route.query)) return props.inactiveClass
if (props.exactQuery && !isEqual(route.query, $route.query)) {
return props.inactiveClass
}
if (props.exactHash && route.hash !== $route.hash) {
return props.inactiveClass

View File

@@ -46,6 +46,10 @@ export default defineComponent({
UIcon
},
inheritAttrs: false,
slots: Object as SlotsType<{
indicator?: { percent: number, value: number }
label?: { percent: number, value: number }
}>,
props: {
value: {
type: Number,
@@ -94,10 +98,6 @@ export default defineComponent({
default: () => ({})
}
},
slots: Object as SlotsType<{
indicator?: { percent: number, value: number }
label?: { percent: number, value: number }
}>,
setup(props) {
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 UIcon from '../elements/Icon.vue'
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 Meter from './Meter.vue'
// @ts-expect-error

View File

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

View File

@@ -5,12 +5,14 @@
</template>
<script lang="ts">
import { provide, ref, defineComponent, onUnmounted, onMounted, readonly } from 'vue'
import type { PropType } from 'vue'
import { provide, ref, type PropType, defineComponent, onUnmounted, onMounted, readonly } from 'vue'
import { useEventBus } from '@vueuse/core'
import type { ZodSchema } from 'zod'
import type { ValidationError as JoiError, Schema as JoiSchema } from 'joi'
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 { FormError, FormEvent, FormEventType, FormSubmitEvent, FormErrorEvent, Form, ValidateReturnSchema } from '../../types/form'
import { useId } from '#imports'
@@ -23,10 +25,14 @@ class FormException extends Error {
}
}
type Schema = PropType<StandardSchemaV1>
type Schema = PropType<ZodSchema>
| PropType<YupObjectSchema<any>>
| PropType<Struct<any, any>>
| 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({
props: {
@@ -54,8 +60,6 @@ export default defineComponent({
const formId = useId()
const bus = useEventBus<FormEvent>(`form-${formId}`)
const parsedValue = ref(null)
onMounted(() => {
bus.on(async (event) => {
if (event.type !== 'submit' && props.validateOn?.includes(event.type)) {
@@ -83,7 +87,7 @@ export default defineComponent({
if (errors) {
errs = errs.concat(errors)
} else {
parsedValue.value = result
Object.assign(props.state, result)
}
}
@@ -126,7 +130,7 @@ export default defineComponent({
if (props.validateOn?.includes('submit')) {
await validate()
}
event.data = props.schema ? parsedValue.value : props.state
event.data = props.state
emit('submit', event)
} catch (error) {
if (!(error instanceof FormException)) {
@@ -206,18 +210,26 @@ function isJoiError(error: any): error is JoiError {
return error.isJoi === true
}
export function isStandardSchema(schema: any): schema is StandardSchemaV1 {
return '~standard' in schema
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 '_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,
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>> {
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) {
const output = ('value' in result ? result.value : null)
const output = ('output' in result
? result.output
: 'value' in result
? result.value
: null)
return {
errors: null,
result: output
@@ -225,7 +237,7 @@ export async function validateStandardSchema(
}
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
}))
@@ -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>> {
const [err, result] = schema.validate(state)
if (err) {
@@ -287,7 +321,7 @@ async function validateYupSchema(
schema: YupObjectSchema<any>
): Promise<ValidateReturnSchema<typeof state>> {
try {
const result = await schema.validate(state, { abortEarly: false })
const result = schema.validateSync(state, { abortEarly: false })
return {
errors: null,
result
@@ -310,10 +344,12 @@ async function validateYupSchema(
}
function parseSchema(state: any, schema: Schema): Promise<ValidateReturnSchema<typeof state>> {
if (isStandardSchema(schema)) {
return validateStandardSchema(state, schema)
if (isZodSchema(schema)) {
return validateZodSchema(state, schema)
} else if (isJoiSchema(schema)) {
return validateJoiSchema(state, schema)
} else if (isValibotSchema(schema)) {
return validateValibotSchema(state, schema)
} else if (isYupSchema(schema)) {
return validateYupSchema(state, schema)
} else if (isSuperStructSchema(schema)) {

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