Compare commits

..

3 Commits

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

View File

@@ -5,7 +5,7 @@ body:
- type: markdown
attributes:
value: |
Before reporting a bug, please make sure that you have read through our [v3 documentation](https://ui.nuxt.com/) and existing [issues](https://github.com/nuxt/ui/issues?q=is%3Aissue%20is%3Aopen%20sort%3Aupdated-desc%20label%3Av3).
Before reporting a bug, please make sure that you have read through our [v3 documentation](https://ui3.nuxt.dev/) and existing [issues](https://github.com/nuxt/ui/issues?q=is%3Aissue%20is%3Aopen%20sort%3Aupdated-desc%20label%3Av3).
- type: textarea
id: env
attributes:
@@ -37,7 +37,7 @@ body:
id: version
attributes:
label: Version
placeholder: v3.0.0
placeholder: v3.0.0-alpha.x
validations:
required: true
- type: textarea

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,7 +5,7 @@ body:
- type: markdown
attributes:
value: |
Before requesting a feature, please make sure that you have read through our [v3 documentation](https://ui.nuxt.com/) and existing [issues](https://github.com/nuxt/ui/issues?q=is%3Aissue%20is%3Aopen%20sort%3Aupdated-desc%20label%3Av3).
Before requesting a feature, please make sure that you have read through our [v3 documentation](https://ui3.nuxt.dev/) and existing [issues](https://github.com/nuxt/ui/issues?q=is%3Aissue%20is%3Aopen%20sort%3Aupdated-desc%20label%3Av3).
- type: textarea
id: description
attributes:

View File

@@ -5,7 +5,7 @@ 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: textarea
id: description
attributes:

View File

@@ -5,7 +5,7 @@ body:
- type: markdown
attributes:
value: |
Before asking a question, please make sure that you have read through our [v3 documentation](https://ui.nuxt.com/) and existing [issues](https://github.com/nuxt/ui/issues?q=is%3Aissue%20is%3Aopen%20sort%3Aupdated-desc%20label%3Av3).
Before asking a question, please make sure that you have read through our [v3 documentation](https://ui3.nuxt.dev/) and existing [issues](https://github.com/nuxt/ui/issues?q=is%3Aissue%20is%3Aopen%20sort%3Aupdated-desc%20label%3Av3).
- type: textarea
id: description
attributes:

View File

@@ -5,7 +5,7 @@ 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: textarea
id: description
attributes:

View File

@@ -50,164 +50,16 @@ jobs:
run: pnpm run typecheck
- name: Test
run: pnpm run test run
run: pnpm run test
- name: Test (vue)
run: pnpm run test:vue run
run: pnpm run test:vue
- name: Build
run: pnpm run build
- name: Build playground
run: pnpm run dev:build
- name: Build playground (vue)
run: pnpm run dev:vue:build
- name: Build vue fixture
run: pnpm run test:vue:build
- name: Publish
run: pnpx pkg-pr-new publish --compact --no-template --pnpm
starter-nuxt:
needs: build
runs-on: ${{ matrix.os }}
permissions:
contents: read
pull-requests: read
strategy:
matrix:
os: [ubuntu-latest] # macos-latest, windows-latest
node: [22]
steps:
- name: Checkout
uses: actions/checkout@v4
with:
repository: nuxtlabs/nuxt-ui-starter
- name: Store commit SHA
run: |
echo "COMMIT_SHA=$(echo ${{ github.workflow_sha }} | cut -c1-7)" >> $GITHUB_ENV
- name: Install pnpm
uses: pnpm/action-setup@v4
- name: Install node
uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- name: Install latest nuxt/ui
run: pnpm install https://pkg.pr.new/@nuxt/ui@${{ env.COMMIT_SHA }} --lockfile-only
- name: Install dependencies
run: pnpm install
- name: Typecheck
run: pnpm run typecheck
- name: Build
run: pnpm run build
starter-vue:
needs: build
runs-on: ${{ matrix.os }}
permissions:
contents: read
pull-requests: read
strategy:
matrix:
os: [ubuntu-latest] # macos-latest, windows-latest
node: [22]
steps:
- name: Checkout
uses: actions/checkout@v4
with:
repository: nuxtlabs/nuxt-ui-vue-starter
- name: Store commit SHA
run: |
echo "COMMIT_SHA=$(echo ${{ github.workflow_sha }} | cut -c1-7)" >> $GITHUB_ENV
- name: Install pnpm
uses: pnpm/action-setup@v4
- name: Install node
uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- name: Install latest nuxt/ui
run: pnpm install https://pkg.pr.new/@nuxt/ui@${{ env.COMMIT_SHA }} --lockfile-only
- name: Install dependencies
run: pnpm install
- name: Typecheck
run: pnpm run typecheck
- name: Build
run: pnpm run build
nuxt-ui-pro:
needs: build
# Only run this job if not a fork PR (when push event or PR from same repo)
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository
runs-on: ${{ matrix.os }}
permissions:
contents: read
pull-requests: read
strategy:
matrix:
os: [ubuntu-latest] # macos-latest, windows-latest
node: [22]
env:
NUXT_UI_PRO_LICENSE: ${{ secrets.NUXT_UI_PRO_LICENSE }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
repository: nuxt/ui-pro
token: ${{ secrets.NUXT_GITHUB_TOKEN }}
- name: Store commit SHA
run: |
echo "COMMIT_SHA=$(echo ${{ github.workflow_sha }} | cut -c1-7)" >> $GITHUB_ENV
- name: Install pnpm
uses: pnpm/action-setup@v4
- name: Install node
uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- name: Install latest nuxt/ui
run: pnpm install https://pkg.pr.new/@nuxt/ui@${{ env.COMMIT_SHA }} --lockfile-only
- name: Install dependencies
run: pnpm install
- name: Prepare
run: pnpm run dev:prepare
- name: Typecheck
run: pnpm run typecheck
- name: Build
run: pnpm run build

View File

@@ -10,16 +10,14 @@ jobs:
permissions:
issues: write
pull-requests: write
steps:
- uses: actions/stale@v9
with:
days-before-stale: -1 # Issues and PR will never be flagged stale automatically.
stale-issue-label: 'needs reproduction' # Label that flags an issue as stale.
only-labels: 'needs reproduction' # Only process these issues
days-before-issue-close: 7
ignore-updates: true
remove-stale-when-updated: false
close-issue-message: This issue was closed because it was open for 7 days without a reproduction.
close-issue-label: closed-by-bot
operations-per-run: 300 #default 30
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,102 +1,5 @@
# Changelog
## [3.0.2](https://github.com/nuxt/ui/compare/v3.0.1...v3.0.2) (2025-03-28)
### Features
* **Calendar:** allow year and month buttons styling ([#3672](https://github.com/nuxt/ui/issues/3672)) ([4a2b77d](https://github.com/nuxt/ui/commit/4a2b77d86c28806234002340eda39de4dc78cce0))
* **locale:** add Armenian language ([#3664](https://github.com/nuxt/ui/issues/3664)) ([c76f590](https://github.com/nuxt/ui/commit/c76f5900970e3f5c451192b1207ccea04771e8b3))
* **Table:** add `empty` prop ([afff54f](https://github.com/nuxt/ui/commit/afff54fecd31497238461e0a44abd8668ed734c3))
### Bug Fixes
* **Avatar:** proxy `$attrs` to default slot ([#3712](https://github.com/nuxt/ui/issues/3712)) ([88f349d](https://github.com/nuxt/ui/commit/88f349d0d74eb1c2ce5066818731759c25a9e83e))
* **Button:** use `focus:outline-none` instead of `focus:outline-hidden` ([c231fe5](https://github.com/nuxt/ui/commit/c231fe5f26ca7614df46a7ec8a5ce7f4ec8884e7)), closes [#3658](https://github.com/nuxt/ui/issues/3658)
* **CommandPalette:** use `group.id` as key ([bc61d29](https://github.com/nuxt/ui/commit/bc61d29cce531715a6279444845f02a002a22af7))
* **components:** improve generic types ([#3331](https://github.com/nuxt/ui/issues/3331)) ([b998354](https://github.com/nuxt/ui/commit/b9983549a4b743724ea3ef99cc4a243f5ca41e53))
* **Container:** add `w-full` class ([df00149](https://github.com/nuxt/ui/commit/df001495980647cab1e67fd16154f1bc778de5e2))
* **defineLocale/defineShortcuts:** remove `@__NO_SIDE_EFFECTS__` ([82e2665](https://github.com/nuxt/ui/commit/82e26655a40782555299516f32a76046fa0dbd3a))
* **Drawer:** remove `fadeFromIndex` prop proxy ([f7604e5](https://github.com/nuxt/ui/commit/f7604e565f717001a4d4c2974cf23559a3f01c21))
* **Form:** clear dirty state after submit ([#3692](https://github.com/nuxt/ui/issues/3692)) ([3dd88ba](https://github.com/nuxt/ui/commit/3dd88bacecb2945efba8cc3cb4fe59fcbc056e9a))
* **FormField:** add `help` to `aria-describedby` attribute ([#3691](https://github.com/nuxt/ui/issues/3691)) ([20c3392](https://github.com/nuxt/ui/commit/20c33920d005332db3c83f33a8c54c7c227ce0a0))
* **InputMenu/SelectMenu:** empty search results ([94b6e52](https://github.com/nuxt/ui/commit/94b6e520f5ccf011204e953421fcc5b44b637e51))
* **InputMenu:** reset `searchTerm` on `update:open` ([3074632](https://github.com/nuxt/ui/commit/3074632523e67fa6a0ad3d9a71e5692c285bdc3a)), closes [#3620](https://github.com/nuxt/ui/issues/3620)
* **Link:** handle `aria-current` like `NuxtLink` / `RouterLink` ([c531d02](https://github.com/nuxt/ui/commit/c531d0248be7863980a1f676643c2dea8301c009))
* **Link:** prevent `active="true"` binding on html ([d73768b](https://github.com/nuxt/ui/commit/d73768b70453d60dd4186a996c1cf808b0294bf6))
* **Link:** properly pick all `aria-*` & `data-*` attrs ([ade16b7](https://github.com/nuxt/ui/commit/ade16b76cf535924a8d0f402b4d5d65cb67a55eb))
* **Link:** proxy `onClick` ([370054b](https://github.com/nuxt/ui/commit/370054b20c0201c9dba84ddfcd1e916594619b93)), closes [#3631](https://github.com/nuxt/ui/issues/3631)
* **NavigationMenu:** add `z-index` on viewport ([0095d89](https://github.com/nuxt/ui/commit/0095d8916bf361c0c89972e2f86b79850510c6a9)), closes [#3654](https://github.com/nuxt/ui/issues/3654)
* **Switch:** prevent transition on focus outline ([68787b2](https://github.com/nuxt/ui/commit/68787b26fdf2bd5f9d9e812e5bfddb19abe45d1d))
* **Table:** wrong condition on `caption` slot ([4ebb94c](https://github.com/nuxt/ui/commit/4ebb94cd7ef909b3547bce0922f75fe3ff74de4c))
* **Tabs:** remove `focus:outline-hidden` class ([1769d5e](https://github.com/nuxt/ui/commit/1769d5ed6ea46b1f7eafdc48cb6456512229f98b))
* **types:** add missing export for ButtonGroup ([#3709](https://github.com/nuxt/ui/issues/3709)) ([e7e6745](https://github.com/nuxt/ui/commit/e7e674559981177ad08be42418746060d7737df9))
* **useOverlay:** refine `open` method type to infer close emit return type ([#3716](https://github.com/nuxt/ui/issues/3716)) ([bd99c2d](https://github.com/nuxt/ui/commit/bd99c2d850d57baccc51e049c0b578a6fc6ab431))
* **vue:** mock `nuxtApp.hooks` & `useRuntimeHook` ([23bfeb9](https://github.com/nuxt/ui/commit/23bfeb937004d619187a67fb43e4c76b13d00069))
## [3.0.1](https://github.com/nuxt/ui/compare/v3.0.0...v3.0.1) (2025-03-21)
### ⚠ BREAKING CHANGES
* **Form:** drop explicit support for `zod` and `valibot` (#3617)
### Features
* **components:** handle events in `content` prop ([5dec0e1](https://github.com/nuxt/ui/commit/5dec0e16e28549b8833aaab17a87fada63d6598c))
* **locale:** add Catalan language ([#3550](https://github.com/nuxt/ui/issues/3550)) ([53cf1b4](https://github.com/nuxt/ui/commit/53cf1b4c14a2a0e076e1e77688852e6bd0a28a74))
* **locale:** add Central Kurdish language ([#3566](https://github.com/nuxt/ui/issues/3566)) ([b2034cc](https://github.com/nuxt/ui/commit/b2034ccc91eec6a2842c6f83d159e5aa6fd5f2fd))
* **locale:** add Romanian language ([#3587](https://github.com/nuxt/ui/issues/3587)) ([0229b0f](https://github.com/nuxt/ui/commit/0229b0fd4644a97db7eb3154c3c87a26634dcfbb))
* **locale:** add Urdu language ([#3611](https://github.com/nuxt/ui/issues/3611)) ([126ba23](https://github.com/nuxt/ui/commit/126ba2326f8153e155e1013db92c6ee417117610))
* **locale:** add Uzbek language ([#3548](https://github.com/nuxt/ui/issues/3548)) ([302e04b](https://github.com/nuxt/ui/commit/302e04bd77ae8b165046b264c8d23626e92f8fb5))
### Bug Fixes
* **Calendar:** grey out days outside of displayed month ([#3639](https://github.com/nuxt/ui/issues/3639)) ([a516866](https://github.com/nuxt/ui/commit/a5168666b7dff08e714d57f497737e7a670f457c))
* **ContextMenu/DropdownMenu:** remove `any` from `proxySlots` ([#3623](https://github.com/nuxt/ui/issues/3623)) ([764c41a](https://github.com/nuxt/ui/commit/764c41a0c60dd1c12d39a86af9f5f11b9e6cdc8c))
* **Modal/Slideover/Toast:** prevent unnecessary close instantiation ([f4c417d](https://github.com/nuxt/ui/commit/f4c417d9ef5409b52084bdf9d8cbccee3139709f))
* **module:** handle tailwindcss import without `theme(static)` ([#3630](https://github.com/nuxt/ui/issues/3630)) ([ecff9ab](https://github.com/nuxt/ui/commit/ecff9abc720bdda3a279d5bcfb7b477ff885f2e4))
* **module:** mark functions used in exports as pure ([#3604](https://github.com/nuxt/ui/issues/3604)) ([57efc78](https://github.com/nuxt/ui/commit/57efc78a3b3fa4a54bcd13df47d570a18fba2bc4))
* **RadioGroup:** handle `disabled` on items ([fe0bd83](https://github.com/nuxt/ui/commit/fe0bd83d11b0dfa53b58d423bc917f8e21d73444)), closes [nuxt/ui-pro#911](https://github.com/nuxt/ui-pro/issues/911)
* **Table:** allow links to be opened when [@select](https://github.com/select) is used ([#3580](https://github.com/nuxt/ui/issues/3580)) ([e80cc15](https://github.com/nuxt/ui/commit/e80cc1592fb244dd7692486a4c1ca5b1c2008112))
* **types:** add missing export for Icon ([#3568](https://github.com/nuxt/ui/issues/3568)) ([5e62493](https://github.com/nuxt/ui/commit/5e624933216db95cbfd1b8034b2eb0f13846ae55))
* **unplugin:** include `@compodium/examples` in auto-imports paths ([#3585](https://github.com/nuxt/ui/issues/3585)) ([cc504b8](https://github.com/nuxt/ui/commit/cc504b8a4b69dd76b49659d5c206ef23dcb9e475))
* **useLocale:** unique symbol to use in `@nuxt/ui-pro` ([#3603](https://github.com/nuxt/ui/issues/3603)) ([dec2730](https://github.com/nuxt/ui/commit/dec2730aaea1327434837cfa022ea04056757cbf))
* **vue:** missing unhead context ([#3589](https://github.com/nuxt/ui/issues/3589)) ([0897e9e](https://github.com/nuxt/ui/commit/0897e9ef05fbee4f021f317bb7c2d0b7007f1b75))
### Code Refactoring
* **Form:** drop explicit support for `zod` and `valibot` ([#3617](https://github.com/nuxt/ui/issues/3617)) ([9a4bb34](https://github.com/nuxt/ui/commit/9a4bb34d7d14add0a3199103f4b583e8307d1d6d))
## [3.0.0](https://github.com/nuxt/ui/compare/v3.0.0-beta.4...v3.0.0) (2025-03-12)
## [3.0.0-beta.4](https://github.com/nuxt/ui/compare/v3.0.0-beta.3...v3.0.0-beta.4) (2025-03-12)
### Features
* **Form:** global errors ([#3482](https://github.com/nuxt/ui/issues/3482)) ([6e03d9c](https://github.com/nuxt/ui/commit/6e03d9c6efc8f4cfc306813e733d7d3e03706323))
* **Input/Textarea:** allow `null` value in model ([#3415](https://github.com/nuxt/ui/issues/3415)) ([cfe9b2e](https://github.com/nuxt/ui/commit/cfe9b2ecf34827bc11a5281a069988ab96030047))
* **useLocale:** handle generic messages ([#3100](https://github.com/nuxt/ui/issues/3100)) ([a9c8eb3](https://github.com/nuxt/ui/commit/a9c8eb3f60a10d1a71632991c9db594716b0fba1))
### Bug Fixes
* **Button:** missing import ([21dbf01](https://github.com/nuxt/ui/commit/21dbf01888a161a9d8ac6eb0d957c1342f6cc30d)), closes [nuxt/ui#3417](https://github.com/nuxt/ui/issues/3417)
* **Form:** input blur validation on submit ([#3504](https://github.com/nuxt/ui/issues/3504)) ([97c8098](https://github.com/nuxt/ui/commit/97c8098d4a35c392719ae179d36aa008d6f8f78a))
* **vue:** prevent calling `useHead` in colors ([5ecd227](https://github.com/nuxt/ui/commit/5ecd2271ca86087cb805548397d75c38763ad412))
## [3.0.0-beta.3](https://github.com/nuxt/ui/compare/v3.0.0-beta.2...v3.0.0-beta.3) (2025-03-07)
### Features
* **Button:** handle `active` state ([bd2d484](https://github.com/nuxt/ui/commit/bd2d4848d246a3d5930f8059913f5a1a0abe29fd)), closes [#3417](https://github.com/nuxt/ui/issues/3417)
* **Table:** add `loading` slot ([99e531d](https://github.com/nuxt/ui/commit/99e531d8dfb7954322b7ab7feda3d8814c6d8d02)), closes [#3444](https://github.com/nuxt/ui/issues/3444)
### Bug Fixes
* **InputMenu/SelectMenu:** proxy `required` in root props ([60b7e2d](https://github.com/nuxt/ui/commit/60b7e2d69e80afa7e221855dcec46479d0ca5c6c))
* **InputMenu:** wrong `required` in multiple mode ([01fa230](https://github.com/nuxt/ui/commit/01fa230eae4b6623c5fd71cc218d114d9f6f0f25)), closes [#2741](https://github.com/nuxt/ui/issues/2741)
* **Pagination:** add missing slots ([a47c5ff](https://github.com/nuxt/ui/commit/a47c5ff46616eafee3158cb9801183965f5f9874)), closes [#3441](https://github.com/nuxt/ui/issues/3441)
* **Pagination:** wrong next link ([e823022](https://github.com/nuxt/ui/commit/e823022b19bb172d2e5fabb9144b4a4286a25a5f)), closes [#3008](https://github.com/nuxt/ui/issues/3008)
* **templates:** prevent overriding existing colors ([ccbd89c](https://github.com/nuxt/ui/commit/ccbd89c908fe8af54c7d723dd12da5b7f3906c8f)), closes [#3426](https://github.com/nuxt/ui/issues/3426)
## [3.0.0-beta.2](https://github.com/nuxt/ui/compare/v3.0.0-beta.1...v3.0.0-beta.2) (2025-02-28)
### Bug Fixes

View File

@@ -11,31 +11,31 @@
[![License][license-src]][license-href]
[![Nuxt][nuxt-src]][nuxt-href]
Nuxt UI harnesses the combined strengths of [Reka UI](https://reka-ui.com/), [Tailwind CSS](https://tailwindcss.com/), and [Tailwind Variants](https://www.tailwind-variants.org/) to offer developers an unparalleled set of tools for creating sophisticated, accessible, and highly performant user interfaces.
We're thrilled to introduce Nuxt UI v3, a significant upgrade to our UI library that delivers extensive improvements and robust new capabilities. This major update harnesses the combined strengths of [Reka UI](https://reka-ui.com/), [Tailwind CSS v4](https://tailwindcss.com/), and [Tailwind Variants](https://www.tailwind-variants.org/) to offer developers an unparalleled set of tools for creating sophisticated, accessible, and highly performant user interfaces.
> [!NOTE]
> You are on the `v3` development branch, check out the [v2 branch](https://github.com/nuxt/ui/tree/v2) for Nuxt UI v2.
> You are on the `v3` development branch, check out the [dev branch](https://github.com/nuxt/ui/tree/dev) for Nuxt UI v2.
## Documentation
Visit https://ui.nuxt.com to explore the documentation.
Visit https://ui3.nuxt.dev to explore the documentation.
## Installation
```bash [pnpm]
pnpm add @nuxt/ui
pnpm add @nuxt/ui@next
```
```bash [yarn]
yarn add @nuxt/ui
yarn add @nuxt/ui@next
```
```bash [npm]
npm install @nuxt/ui
npm install @nuxt/ui@next
```
```bash [bun]
bun add @nuxt/ui
bun add @nuxt/ui@next
```
### Nuxt
@@ -51,11 +51,11 @@ export default defineNuxtConfig({
2. Import Tailwind CSS and Nuxt UI in your CSS:
```css [assets/css/main.css]
@import "tailwindcss";
@import "tailwindcss" theme(static);
@import "@nuxt/ui";
```
Learn more in the [installation guide](https://ui.nuxt.com/getting-started/installation/nuxt).
Learn more in the [installation guide](https://ui3.nuxt.dev/getting-started/installation/nuxt).
### Vue
@@ -98,22 +98,11 @@ app.mount('#app')
3. Import Tailwind CSS and Nuxt UI in your CSS:
```css [assets/main.css]
@import "tailwindcss";
@import "tailwindcss" theme(static);
@import "@nuxt/ui";
```
Learn more in the [installation guide](https://ui.nuxt.com/getting-started/installation/vue).
## Contribution
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://ui.nuxt.com/getting-started/contribution) to share your suggestions.
## Local Development
Follow the docs to [set up your local development environment](https://ui.nuxt.com/getting-started/contribution#local-development) and contribute.
Learn more in the [installation guide](https://ui3.nuxt.dev/getting-started/installation/vue).
## Credits
@@ -130,7 +119,7 @@ Follow the docs to [set up your local development environment](https://ui.nuxt.c
Licensed under the [MIT license](https://github.com/nuxt/ui/blob/v3/LICENSE.md).
<!-- Badges -->
[npm-version-src]: https://img.shields.io/npm/v/@nuxt/ui/latest.svg?style=flat&colorA=18181B&colorB=28CF8D
[npm-version-src]: https://img.shields.io/npm/v/@nuxt/ui/next.svg?style=flat&colorA=18181B&colorB=28CF8D
[npm-version-href]: https://npmjs.com/package/@nuxt/ui
[npm-downloads-src]: https://img.shields.io/npm/dm/@nuxt/ui.svg?style=flat&colorA=18181B&colorB=28CF8D

View File

@@ -6,6 +6,9 @@ export default defineBuildConfig({
'./src/unplugin',
'./src/vite'
],
rollup: {
emitCJS: true
},
replace: {
'process.env.DEV': 'false'
},

View File

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

View File

@@ -33,7 +33,7 @@ const component = ({ name, primitive, pro, prose, content }) => {
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/${path}/${prose ? 'prose/' : ''}${content ? 'content/' : ''}${kebabName}'
import { tv } from '../utils/tv'
import { tv } from '${pro ? '#ui/utils/tv' : '../utils/tv'}'
const appConfig${camelName} = _appConfig as AppConfig & { ${key}: { ${prose ? 'prose: { ' : ''}${camelName}: Partial<typeof theme> } }${prose ? ' }' : ''}
@@ -76,7 +76,7 @@ import type { ${upperName}RootProps, ${upperName}RootEmits } from 'reka-ui'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/${path}/${prose ? 'prose/' : ''}${content ? 'content/' : ''}${kebabName}'
import { tv } from '../utils/tv'
import { tv } from '${pro ? '#ui/utils/tv' : '../utils/tv'}'
const appConfig${camelName} = _appConfig as AppConfig & { ${key}: { ${prose ? 'prose: { ' : ''}${camelName}: Partial<typeof theme> } }${prose ? ' }' : ''}

View File

@@ -1,6 +1,7 @@
<script setup lang="ts">
import { withoutTrailingSlash } from 'ufo'
// import { withoutTrailingSlash } from 'ufo'
import colors from 'tailwindcss/colors'
// import { debounce } from 'perfect-debounce'
const route = useRoute()
const appConfig = useAppConfig()
@@ -11,8 +12,17 @@ const { data: files } = useLazyAsyncData('search', () => queryCollectionSearchSe
server: false
})
const searchTerm = ref('')
// watch(searchTerm, debounce((query: string) => {
// if (!query) {
// return
// }
// useTrackEvent('Search', { props: { query: `${query} - ${searchTerm.value?.commandPaletteRef.results.length} results` } })
// }, 500))
const links = useLinks()
const searchLinks = useSearchLinks()
const color = computed(() => colorMode.value === 'dark' ? (colors as any)[appConfig.ui.colors.neutral][900] : 'white')
const radius = computed(() => `:root { --ui-radius: ${appConfig.theme.radius}rem; }`)
const blackAsPrimary = computed(() => appConfig.theme.blackAsPrimary ? `:root { --ui-primary: black; } .dark { --ui-primary: white; }` : ':root {}')
@@ -23,8 +33,8 @@ useHead({
{ key: 'theme-color', name: 'theme-color', content: color }
],
link: [
{ rel: 'icon', type: 'image/svg+xml', href: '/icon.svg' },
{ rel: 'canonical', href: `https://ui.nuxt.com${withoutTrailingSlash(route.path)}` }
{ rel: 'icon', type: 'image/svg+xml', href: '/icon.svg' }
// { rel: 'canonical', href: `https://ui.nuxt.com${withoutTrailingSlash(route.path)}` }
],
style: [
{ innerHTML: radius, id: 'nuxt-ui-radius', tagPriority: -2 },
@@ -51,7 +61,7 @@ provide('navigation', mappedNavigation)
<NuxtLoadingIndicator color="var(--ui-primary)" :height="2" />
<template v-if="!route.path.startsWith('/examples')">
<Banner />
<!-- <Banner /> -->
<Header :links="links" />
</template>
@@ -65,7 +75,7 @@ provide('navigation', mappedNavigation)
<ClientOnly>
<LazyUContentSearch
:links="searchLinks"
v-model:search-term="searchTerm"
:files="files"
:groups="[{
id: 'framework',
@@ -85,5 +95,5 @@ provide('navigation', mappedNavigation)
</template>
<style>
/* Safelist (do not remove): [&>div]:*:my-0 [&>div]:*:w-full h-64 !px-0 !py-0 !pt-0 !pb-0 !p-0 !justify-start !justify-end !min-h-96 h-136 */
/* Safelist (do not remove): [&>div]:*:my-0 [&>div]:*:w-full h-64 !px-0 !py-0 !pt-0 !pb-0 !p-0 !justify-start !min-h-96 h-136 */
</style>

View File

@@ -1,50 +0,0 @@
<script setup lang="ts">
const el = ref<HTMLDivElement | null>(null)
onMounted(() => {
if (!el.value) {
return
}
const script = document.createElement('script')
script.setAttribute('type', 'text/javascript')
script.setAttribute('src', 'https://cdn.carbonads.com/carbon.js?serve=CWYIVK3E&placement=uinuxtcom')
script.setAttribute('id', '_carbonads_js')
el.value?.appendChild(script)
})
</script>
<template>
<div ref="el" class="carbon" />
</template>
<style scoped>
@reference "../assets/css/main.css";
.carbon :deep(#carbonads) {
@apply relative border border-(--ui-border) rounded-[calc(var(--ui-radius)*1.5)] hover:bg-(--ui-bg-elevated)/50 w-full transition-colors min-h-[220px] p-2;
.carbon-img {
@apply flex justify-center w-full;
& > img {
@apply !max-w-full w-full rounded-(--ui-radius);
}
}
.carbon-text {
@apply text-sm text-(--ui-text-muted) transition-colors text-center text-pretty flex pt-2;
}
.carbon-poweredby {
@apply block text-xs text-center text-(--ui-text-muted) pt-2;
}
&:hover {
.carbon-text {
@apply text-(--ui-text);
}
}
}
</style>

View File

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

View File

@@ -1,51 +0,0 @@
<script setup lang="ts">
const endDate = new Date('2025-03-14T23:59:59Z')
const second = 1000
const minute = second * 60
const hour = minute * 60
const day = hour * 24
function getCountdown() {
const distance = Math.floor((endDate.getTime() - Date.now()))
return {
day: Math.floor(distance / day),
hour: Math.floor((distance % (day)) / (hour)),
minute: Math.floor((distance % (hour)) / (minute)),
second: Math.floor((distance % (minute)) / (second)),
distance
}
}
const countdown = ref(getCountdown())
let interval: any
if (countdown.value.distance > 0) {
onMounted(() => {
interval = setInterval(() => {
countdown.value = getCountdown()
if (countdown.value.distance <= 0) {
clearInterval(interval)
}
}, 1000)
})
}
const plural = (value: number) => (value === 1 ? '' : 's')
const double = (value: number) => (value < 10 ? `0${value}` : value)
</script>
<template>
<div>
<p class="font-semibold text-gray-900 dark:text-white text-sm mb-3">
Nuxt UI v3 launch offer ends in:
</p>
<div class="flex items-center justify-center gap-2 text-center">
<template v-for="(value, key) in countdown" :key="key">
<div v-if="key !== 'distance'" class="flex flex-col items-center gap-2">
<UBadge color="primary" class="w-14 h-14 font-bold text-2xl flex items-center justify-center tabular-nums" variant="subtle">
{{ double(value) }}
</UBadge>
<span class="text-[10px] font-semibold text-gray-900 dark:text-white tracking-wide tabular-nums uppercase">{{ key }}{{ plural(value) }}</span>
</div>
</template>
</div>
</div>
</template>

View File

@@ -2,8 +2,8 @@
const route = useRoute()
const links = [{
label: 'Team',
to: '/team'
label: 'Figma',
to: '/figma'
}, {
label: 'Roadmap',
to: '/roadmap'

View File

@@ -22,20 +22,8 @@ onMounted(() => {
const navigation = inject<Ref<ContentNavigationItem[]>>('navigation')
const githubLink = computed(() => {
return `https://github.com/nuxt/${value.value}`
})
const desktopLinks = computed(() => props.links.map(({ icon, ...link }) => link))
const mobileLinks = computed(() => [
...props.links.map(link => ({ ...link, defaultOpen: link.children && route.path.startsWith(link.to as string) })),
{
label: 'Open on GitHub',
to: githubLink.value,
icon: 'i-simple-icons-github',
target: '_blank'
}
])
const mobileLinks = computed(() => props.links.map(link => ({ ...link, defaultOpen: link.children && route.path.startsWith(link.to as string) })))
</script>
<template>
@@ -53,7 +41,7 @@ const mobileLinks = computed(() => [
<UDropdownMenu
v-slot="{ open }"
:modal="false"
:items="[{ label: `v${config.version}`, active: true, color: 'primary', checked: true, type: 'checkbox' }, { label: module === 'ui-pro' ? 'v1.7.1' : 'v2.21.1', to: module === 'ui-pro' ? 'https://ui2.nuxt.com/pro' : 'https://ui2.nuxt.com' }]"
:items="[{ label: `v${config.version}`, active: true, color: 'primary', checked: true, type: 'checkbox' }, { label: module === 'ui-pro' ? 'v1.5' : 'v2.19', to: module === 'ui-pro' ? 'https://ui.nuxt.com/pro' : 'https://ui.nuxt.com' }]"
:ui="{ content: 'w-(--reka-dropdown-menu-trigger-width) min-w-0' }"
size="xs"
>
@@ -85,7 +73,7 @@ const mobileLinks = computed(() => [
:key="value"
color="neutral"
variant="ghost"
:to="githubLink"
:to="`https://github.com/nuxt/${value}`"
target="_blank"
icon="i-simple-icons-github"
aria-label="GitHub"
@@ -94,7 +82,7 @@ const mobileLinks = computed(() => [
</template>
<template #body>
<UNavigationMenu orientation="vertical" :items="mobileLinks" class="-mx-2.5" />
<UNavigationMenu orientation="vertical" :items="mobileLinks" class="-mx-2.5" default-open />
<USeparator type="dashed" class="mt-4 mb-6" />

View File

@@ -1,6 +1,4 @@
<script setup lang="ts">
import { kebabCase } from 'scule'
interface Star {
x: number
y: number
@@ -14,7 +12,6 @@ const props = withDefaults(defineProps<{
color?: string
size?: { min: number, max: number }
speed?: 'slow' | 'normal' | 'fast'
isIndex?: boolean
}>(), {
starCount: 50,
color: 'var(--ui-primary)',
@@ -22,12 +19,9 @@ const props = withDefaults(defineProps<{
min: 1,
max: 3
}),
speed: 'normal',
isIndex: false
speed: 'normal'
})
const route = useRoute()
// Generate random stars
const generateStars = (count: number): Star[] => {
return Array.from({ length: count }, () => {
@@ -41,7 +35,7 @@ const generateStars = (count: number): Star[] => {
}
// Generate all stars
const stars = useState<Star[]>(`${kebabCase(route.path)}-sky`, () => generateStars(props.starCount))
const stars = ref<Star[]>(generateStars(props.starCount))
// Compute twinkle animation duration based on speed
const twinkleDuration = computed(() => {
@@ -55,21 +49,23 @@ const twinkleDuration = computed(() => {
</script>
<template>
<div class="absolute pointer-events-none z-[-1] overflow-hidden" :class="isIndex ? 'inset-y-0 left-4 right-4 lg:right-[50%]' : 'inset-0'">
<div
v-for="star in stars"
:key="star.id"
class="star absolute"
:style="{
'left': `${star.x}%`,
'top': `${star.y}%`,
'transform': 'translate(-50%, -50%)',
'--star-size': `${star.size}px`,
'--star-color': color,
'--twinkle-delay': `${star.twinkleDelay}s`,
'--twinkle-duration': twinkleDuration
}"
/>
<div class="absolute pointer-events-none z-[-1] inset-y-0 left-4 right-4 lg:right-[50%] overflow-hidden">
<ClientOnly>
<div
v-for="star in stars"
:key="star.id"
class="star absolute"
:style="{
'left': `${star.x}%`,
'top': `${star.y}%`,
'transform': 'translate(-50%, -50%)',
'--star-size': `${star.size}px`,
'--star-color': color,
'--twinkle-delay': `${star.twinkleDelay}s`,
'--twinkle-duration': twinkleDuration
}"
/>
</ClientOnly>
</div>
</template>

View File

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

View File

@@ -1,6 +1,5 @@
<!-- eslint-disable no-useless-escape -->
<script setup lang="ts">
import type { ChipProps } from '@nuxt/ui'
import json5 from 'json5'
import { upperFirst, camelCase, kebabCase } from 'scule'
import { hash } from 'ohash'
@@ -54,8 +53,6 @@ const props = defineProps<{
hide?: string[]
/** List of props to externalize in script setup */
external?: string[]
/** The types of the externalized props */
externalTypes?: string[]
/** List of props to use with `v-model` */
model?: string[]
/** List of props to cast from code and selection */
@@ -212,21 +209,11 @@ ${props.slots?.default}
code += `
<script setup lang="ts">
`
if (props.externalTypes?.length) {
const removeArrayBrackets = (type: string): string => type.endsWith('[]') ? removeArrayBrackets(type.slice(0, -2)) : type
const types = props.externalTypes.map(type => removeArrayBrackets(type))
code += `import type { ${types.join(', ')} } from '@nuxt/ui${props.pro ? '-pro' : ''}'
`
}
for (const [i, key] of props.external.entries()) {
for (const key of props.external) {
const cast = props.cast?.[key]
const value = cast ? castMap[cast]!.template(componentProps[key]) : json5.stringify(componentProps[key], null, 2)?.replace(/,([ |\t\n]+[}|\]])/g, '$1')
const type = props.externalTypes?.[i] ? `<${props.externalTypes[i]}>` : ''
code += `const ${key === 'modelValue' ? 'value' : key} = ref${type}(${value})
code += `const ${key === 'modelValue' ? 'value' : key} = ref(${value})
`
}
code += `<\/script>
@@ -359,7 +346,7 @@ const { data: ast } = await useAsyncData(`component-code-${name}-${hash({ props:
inset
standalone
:color="(modelValue as any)"
:size="(ui.itemLeadingChipSize() as ChipProps['size'])"
:size="ui.itemLeadingChipSize()"
class="size-2"
/>
</template>

View File

@@ -1,5 +1,4 @@
<script setup lang="ts">
import type { ChipProps } from '@nuxt/ui'
import { camelCase } from 'scule'
import { useElementSize } from '@vueuse/core'
import { get, set } from '#ui/utils'
@@ -186,7 +185,7 @@ const urlSearchParams = computed(() => {
inset
standalone
:color="(modelValue as any)"
:size="(ui.itemLeadingChipSize() as ChipProps['size'])"
:size="ui.itemLeadingChipSize()"
class="size-2"
/>
</template>

View File

@@ -11,23 +11,19 @@ function getEmojiFlag(locale: string): string {
const languageToCountry: Record<string, string> = {
ar: 'sa', // Arabic -> Saudi Arabia
bn: 'bd', // Bengali -> Bangladesh
ca: 'es', // Catalan -> Spain
ckb: 'iq', // Central Kurdish -> Iraq
cs: 'cz', // Czech -> Czech Republic (note: modern country code is actually 'cz')
da: 'dk', // Danish -> Denmark
el: 'gr', // Greek -> Greece
en: 'gb', // English -> Great Britain
et: 'ee', // Estonian -> Estonia
en: 'gb', // English -> Great Britain
he: 'il', // Hebrew -> Israel
hi: 'in', // Hindi -> India
hy: 'am', // Armenian -> Armenia
ja: 'jp', // Japanese -> Japan
km: 'kh', // Khmer -> Cambodia
ko: 'kr', // Korean -> South Korea
nb: 'no', // Norwegian Bokmål -> Norway
sv: 'se', // Swedish -> Sweden
uk: 'ua', // Ukrainian -> Ukraine
ur: 'pk', // Urdu -> Pakistan
vi: 'vn' // Vietnamese -> Vietnam
}

View File

@@ -1,7 +1,5 @@
<script setup lang="ts">
import type { AccordionItem } from '@nuxt/ui'
const items: AccordionItem[] = [
const items = [
{
label: 'Icons',
icon: 'i-lucide-smile'

View File

@@ -1,7 +1,5 @@
<script setup lang="ts">
import type { AccordionItem } from '@nuxt/ui'
const items: AccordionItem[] = [
const items = [
{
label: 'Icons',
icon: 'i-lucide-smile'

View File

@@ -1,6 +1,4 @@
<script setup lang="ts">
import type { AccordionItem } from '@nuxt/ui'
const items = [
{
label: 'Icons',
@@ -10,7 +8,7 @@ const items = [
{
label: 'Colors',
icon: 'i-lucide-swatch-book',
slot: 'colors' as const,
slot: 'colors',
content: 'Choose a primary and a neutral color from your Tailwind CSS theme.'
},
{
@@ -18,7 +16,7 @@ const items = [
icon: 'i-lucide-box',
content: 'You can customize components by using the `class` / `ui` props or in your app.config.ts.'
}
] satisfies AccordionItem[]
]
</script>
<template>

View File

@@ -1,33 +0,0 @@
<script setup lang="ts">
import type { AccordionItem } from '@nuxt/ui'
import { useSortable } from '@vueuse/integrations/useSortable'
const items = shallowRef<AccordionItem[]>([
{
label: 'Icons',
icon: 'i-lucide-smile',
content: 'You have nothing to do, @nuxt/icon will handle it automatically.'
},
{
label: 'Colors',
icon: 'i-lucide-swatch-book',
slot: 'colors' as const,
content: 'Choose a primary and a neutral color from your Tailwind CSS theme.'
},
{
label: 'Components',
icon: 'i-lucide-box',
content: 'You can customize components by using the `class` / `ui` props or in your app.config.ts.'
}
])
const accordion = useTemplateRef<HTMLElement>('accordion')
useSortable(accordion, items, {
animation: 150
})
</script>
<template>
<UAccordion ref="accordion" :items="items" />
</template>

View File

@@ -1,7 +1,5 @@
<script setup lang="ts">
import type { AccordionItem } from '@nuxt/ui'
const items: AccordionItem[] = [
const items = [
{
label: 'Icons',
icon: 'i-lucide-smile',

View File

@@ -1,11 +1,9 @@
<script setup lang="ts">
import type { BreadcrumbItem } from '@nuxt/ui'
const items = [{
label: 'Home',
to: '/'
}, {
slot: 'dropdown' as const,
slot: 'dropdown',
icon: 'i-lucide-ellipsis',
children: [{
label: 'Documentation'
@@ -20,7 +18,7 @@ const items = [{
}, {
label: 'Breadcrumb',
to: '/components/breadcrumb'
}] satisfies BreadcrumbItem[]
}]
</script>
<template>

View File

@@ -1,7 +1,5 @@
<script setup lang="ts">
import type { BreadcrumbItem } from '@nuxt/ui'
const items: BreadcrumbItem[] = [{
const items = [{
label: 'Home',
to: '/'
}, {

View File

@@ -1,7 +1,5 @@
<script setup lang="ts">
import type { DropdownMenuItem } from '@nuxt/ui'
const items: DropdownMenuItem[] = [{
const items = [{
label: 'Team',
icon: 'i-lucide-users'
}, {

View File

@@ -11,7 +11,7 @@ const groups = [{
label: 'Billing',
icon: 'i-lucide-credit-card',
kbds: ['meta', 'B'],
slot: 'billing' as const
slot: 'billing'
},
{
label: 'Notifications',
@@ -25,7 +25,7 @@ const groups = [{
}, {
id: 'users',
label: 'Users',
slot: 'users' as const,
slot: 'users',
items: [
{
label: 'Benjamin Canac',

View File

@@ -1,10 +1,8 @@
<script setup lang="ts">
import type { ContextMenuItem } from '@nuxt/ui'
const showSidebar = ref(true)
const showToolbar = ref(false)
const items = computed<ContextMenuItem[]>(() => [{
const items = computed(() => [{
label: 'View',
type: 'label' as const
}, {

View File

@@ -1,7 +1,5 @@
<script setup lang="ts">
import type { ContextMenuItem } from '@nuxt/ui'
const items: ContextMenuItem[][] = [
const items = [
[
{
label: 'View',

View File

@@ -1,9 +1,7 @@
<script setup lang="ts">
import type { ContextMenuItem } from '@nuxt/ui'
const loading = ref(true)
const items: ContextMenuItem[] = [{
const items = [{
label: 'Refresh the Page',
slot: 'refresh'
}, {

View File

@@ -1,43 +0,0 @@
<script lang="ts" setup>
import { createReusableTemplate, useMediaQuery } from '@vueuse/core'
const [DefineFormTemplate, ReuseFormTemplate] = createReusableTemplate()
const isDesktop = useMediaQuery('(min-width: 768px)')
const open = ref(false)
const state = reactive({
email: undefined
})
const title = 'Edit profile'
const description = 'Make changes to your profile here. Click save when you\'re done.'
</script>
<template>
<DefineFormTemplate>
<UForm :state="state" class="space-y-4">
<UFormField label="Email" name="email" required>
<UInput v-model="state.email" placeholder="shadcn@example.com" required />
</UFormField>
<UButton label="Save changes" type="submit" />
</UForm>
</DefineFormTemplate>
<UModal v-if="isDesktop" v-model:open="open" :title="title" :description="description">
<UButton label="Edit profile" color="neutral" variant="outline" />
<template #body>
<ReuseFormTemplate />
</template>
</UModal>
<UDrawer v-else v-model:open="open" :title="title" :description="description">
<UButton label="Edit profile" color="neutral" variant="outline" />
<template #body>
<ReuseFormTemplate />
</template>
</UDrawer>
</template>

View File

@@ -1,6 +1,4 @@
<script setup lang="ts">
import type { DropdownMenuItem } from '@nuxt/ui'
const showBookmarks = ref(true)
const showHistory = ref(false)
const showDownloads = ref(false)
@@ -38,7 +36,7 @@ const items = computed(() => [{
onUpdateChecked(checked: boolean) {
showDownloads.value = checked
}
}] satisfies DropdownMenuItem[])
}])
</script>
<template>

View File

@@ -1,7 +1,5 @@
<script setup lang="ts">
import type { DropdownMenuItem } from '@nuxt/ui'
const items: DropdownMenuItem[][] = [
const items = [
[
{
label: 'View',
@@ -19,7 +17,7 @@ const items: DropdownMenuItem[][] = [
[
{
label: 'Delete',
color: 'error',
color: 'error' as const,
icon: 'i-lucide-trash'
}
]
@@ -29,5 +27,9 @@ const items: DropdownMenuItem[][] = [
<template>
<UDropdownMenu :items="items" :ui="{ content: 'w-48' }">
<UButton label="Open" color="neutral" variant="outline" icon="i-lucide-menu" />
<template #profile-trailing>
<UIcon name="i-lucide-badge-check" class="shrink-0 size-5 text-(--ui-primary)" />
</template>
</UDropdownMenu>
</template>

View File

@@ -1,19 +1,15 @@
<script setup lang="ts">
import type { DropdownMenuItem } from '@nuxt/ui'
const items = [
{
label: 'Profile',
icon: 'i-lucide-user',
slot: 'profile' as const
}, {
label: 'Billing',
icon: 'i-lucide-credit-card'
}, {
label: 'Settings',
icon: 'i-lucide-cog'
}
] satisfies DropdownMenuItem[]
const items = [{
label: 'Profile',
icon: 'i-lucide-user',
slot: 'profile'
}, {
label: 'Billing',
icon: 'i-lucide-credit-card'
}, {
label: 'Settings',
icon: 'i-lucide-cog'
}]
</script>
<template>

View File

@@ -1,24 +1,20 @@
<script setup lang="ts">
import type { DropdownMenuItem } from '@nuxt/ui'
const open = ref(false)
defineShortcuts({
o: () => open.value = !open.value
})
const items: DropdownMenuItem[] = [
{
label: 'Profile',
icon: 'i-lucide-user'
}, {
label: 'Billing',
icon: 'i-lucide-credit-card'
}, {
label: 'Settings',
icon: 'i-lucide-cog'
}
]
const items = [{
label: 'Profile',
icon: 'i-lucide-user'
}, {
label: 'Billing',
icon: 'i-lucide-credit-card'
}, {
label: 'Settings',
icon: 'i-lucide-cog'
}]
</script>
<template>

View File

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

View File

@@ -16,7 +16,7 @@ function onOpen() {
<template>
<UInputMenu
:items="countries"
:items="countries || []"
:loading="status === 'pending'"
label-key="name"
:search-input="{ icon: 'i-lucide-search' }"

View File

@@ -1,6 +1,4 @@
<script setup lang="ts">
import type { AvatarProps } from '@nuxt/ui'
const { data: users, status } = await useFetch('https://jsonplaceholder.typicode.com/users', {
key: 'typicode-users',
transform: (data: { id: number, name: string }[]) => {
@@ -8,7 +6,7 @@ const { data: users, status } = await useFetch('https://jsonplaceholder.typicode
label: user.name,
value: String(user.id),
avatar: { src: `https://i.pravatar.cc/120?img=${user.id}` }
}))
})) || []
},
lazy: true
})
@@ -16,7 +14,7 @@ const { data: users, status } = await useFetch('https://jsonplaceholder.typicode
<template>
<UInputMenu
:items="users"
:items="users || []"
:loading="status === 'pending'"
icon="i-lucide-user"
placeholder="Select user"
@@ -25,7 +23,7 @@ const { data: users, status } = await useFetch('https://jsonplaceholder.typicode
<UAvatar
v-if="modelValue"
v-bind="modelValue.avatar"
:size="(ui.leadingAvatarSize() as AvatarProps['size'])"
:size="ui.leadingAvatarSize()"
:class="ui.leadingAvatar()"
/>
</template>

View File

@@ -1,6 +1,4 @@
<script setup lang="ts">
import type { AvatarProps } from '@nuxt/ui'
const { data: users, status } = await useFetch('https://jsonplaceholder.typicode.com/users', {
key: 'typicode-users-email',
transform: (data: { id: number, name: string, email: string }[]) => {
@@ -9,7 +7,7 @@ const { data: users, status } = await useFetch('https://jsonplaceholder.typicode
email: user.email,
value: String(user.id),
avatar: { src: `https://i.pravatar.cc/120?img=${user.id}` }
}))
})) || []
},
lazy: true
})
@@ -17,7 +15,7 @@ const { data: users, status } = await useFetch('https://jsonplaceholder.typicode
<template>
<UInputMenu
:items="users"
:items="users || []"
:loading="status === 'pending'"
:filter-fields="['label', 'email']"
icon="i-lucide-user"
@@ -28,7 +26,7 @@ const { data: users, status } = await useFetch('https://jsonplaceholder.typicode
<UAvatar
v-if="modelValue"
v-bind="modelValue.avatar"
:size="(ui.leadingAvatarSize() as AvatarProps['size'])"
:size="ui.leadingAvatarSize()"
:class="ui.leadingAvatar()"
/>
</template>

View File

@@ -1,6 +1,4 @@
<script setup lang="ts">
import type { AvatarProps } from '@nuxt/ui'
const searchTerm = ref('')
const searchTermDebounced = refDebounced(searchTerm, 200)
@@ -12,7 +10,7 @@ const { data: users, status } = await useFetch('https://jsonplaceholder.typicode
label: user.name,
value: String(user.id),
avatar: { src: `https://i.pravatar.cc/120?img=${user.id}` }
}))
})) || []
},
lazy: true
})
@@ -21,7 +19,7 @@ const { data: users, status } = await useFetch('https://jsonplaceholder.typicode
<template>
<UInputMenu
v-model:search-term="searchTerm"
:items="users"
:items="users || []"
:loading="status === 'pending'"
ignore-filter
icon="i-lucide-user"
@@ -31,7 +29,7 @@ const { data: users, status } = await useFetch('https://jsonplaceholder.typicode
<UAvatar
v-if="modelValue"
v-bind="modelValue.avatar"
:size="(ui.leadingAvatarSize() as AvatarProps['size'])"
:size="ui.leadingAvatarSize()"
:class="ui.leadingAvatar()"
/>
</template>

View File

@@ -1,6 +1,4 @@
<script setup lang="ts">
import type { InputMenuItem } from '@nuxt/ui'
const items = ref([
{
label: 'benjamincanac',
@@ -25,16 +23,8 @@ const items = ref([
src: 'https://github.com/noook.png',
alt: 'noook'
}
},
{
label: 'sandros94',
value: 'sandros94',
avatar: {
src: 'https://github.com/sandros94.png',
alt: 'sandros94'
}
}
] satisfies InputMenuItem[])
])
const value = ref(items.value[0])
</script>

View File

@@ -1,30 +1,27 @@
<script setup lang="ts">
import type { InputMenuItem, ChipProps } from '@nuxt/ui'
const items = ref([
{
label: 'bug',
value: 'bug',
chip: {
color: 'error'
color: 'error' as const
}
},
{
label: 'feature',
value: 'feature',
chip: {
color: 'success'
color: 'success' as const
}
},
{
label: 'enhancement',
value: 'enhancement',
chip: {
color: 'info'
color: 'info' as const
}
}
] satisfies InputMenuItem[])
])
const value = ref(items.value[0])
</script>
@@ -36,7 +33,7 @@ const value = ref(items.value[0])
v-bind="modelValue.chip"
inset
standalone
:size="(ui.itemLeadingChipSize() as ChipProps['size'])"
:size="ui.itemLeadingChipSize()"
:class="ui.itemLeadingChip()"
/>
</template>

View File

@@ -1,6 +1,4 @@
<script setup lang="ts">
import type { InputMenuItem } from '@nuxt/ui'
const items = ref([
{
label: 'Backlog',
@@ -22,8 +20,7 @@ const items = ref([
value: 'done',
icon: 'i-lucide-circle-check'
}
] satisfies InputMenuItem[])
])
const value = ref(items.value[0])
</script>

View File

@@ -1,11 +1,9 @@
<script setup lang="ts">
import type { NavigationMenuItem } from '@nuxt/ui'
const items = [
{
label: 'Docs',
icon: 'i-lucide-book-open',
slot: 'docs' as const,
slot: 'docs',
children: [
{
label: 'Icons',
@@ -24,7 +22,7 @@ const items = [
{
label: 'Components',
icon: 'i-lucide-box',
slot: 'components' as const,
slot: 'components',
children: [
{
label: 'Link',
@@ -56,7 +54,7 @@ const items = [
label: 'GitHub',
icon: 'i-simple-icons-github'
}
] satisfies NavigationMenuItem[]
]
</script>
<template>

View File

@@ -1,7 +1,5 @@
<script setup lang="ts">
import type { NavigationMenuItem } from '@nuxt/ui'
const items: NavigationMenuItem[] = [
const items = [
{
label: 'Guide',
icon: 'i-lucide-book-open'

View File

@@ -1,7 +1,5 @@
<script setup lang="ts">
import type { NavigationMenuItem } from '@nuxt/ui'
const items: NavigationMenuItem[] = [
const items = [
{
label: 'Guide',
icon: 'i-lucide-book-open',

View File

@@ -40,7 +40,7 @@ const label = ref([])
multiple
placeholder="Search labels..."
:groups="[{ id: 'labels', items }]"
:ui="{ input: '[&>input]:h-8 [&>input]:text-sm' }"
:ui="{ input: '[&>input]:h-8' }"
/>
</template>
</UPopover>

View File

@@ -4,7 +4,8 @@ const { data: countries, status, execute } = await useLazyFetch<{
code: string
emoji: string
}[]>('/api/countries.json', {
immediate: false
immediate: false,
default: () => []
})
function onOpen() {

View File

@@ -1,6 +1,4 @@
<script setup lang="ts">
import type { AvatarProps } from '@nuxt/ui'
const { data: users, status } = await useFetch('https://jsonplaceholder.typicode.com/users', {
key: 'typicode-users',
transform: (data: { id: number, name: string }[]) => {
@@ -8,7 +6,7 @@ const { data: users, status } = await useFetch('https://jsonplaceholder.typicode
label: user.name,
value: String(user.id),
avatar: { src: `https://i.pravatar.cc/120?img=${user.id}` }
}))
})) || []
},
lazy: true
})
@@ -16,7 +14,7 @@ const { data: users, status } = await useFetch('https://jsonplaceholder.typicode
<template>
<USelectMenu
:items="users"
:items="users || []"
:loading="status === 'pending'"
icon="i-lucide-user"
placeholder="Select user"
@@ -26,7 +24,7 @@ const { data: users, status } = await useFetch('https://jsonplaceholder.typicode
<UAvatar
v-if="modelValue"
v-bind="modelValue.avatar"
:size="(ui.leadingAvatarSize() as AvatarProps['size'])"
:size="ui.leadingAvatarSize()"
:class="ui.leadingAvatar()"
/>
</template>

View File

@@ -1,6 +1,4 @@
<script setup lang="ts">
import type { AvatarProps } from '@nuxt/ui'
const { data: users, status } = await useFetch('https://jsonplaceholder.typicode.com/users', {
key: 'typicode-users-email',
transform: (data: { id: number, name: string, email: string }[]) => {
@@ -9,7 +7,7 @@ const { data: users, status } = await useFetch('https://jsonplaceholder.typicode
email: user.email,
value: String(user.id),
avatar: { src: `https://i.pravatar.cc/120?img=${user.id}` }
}))
})) || []
},
lazy: true
})
@@ -17,7 +15,7 @@ const { data: users, status } = await useFetch('https://jsonplaceholder.typicode
<template>
<USelectMenu
:items="users"
:items="users || []"
:loading="status === 'pending'"
:filter-fields="['label', 'email']"
icon="i-lucide-user"
@@ -28,7 +26,7 @@ const { data: users, status } = await useFetch('https://jsonplaceholder.typicode
<UAvatar
v-if="modelValue"
v-bind="modelValue.avatar"
:size="(ui.leadingAvatarSize() as AvatarProps['size'])"
:size="ui.leadingAvatarSize()"
:class="ui.leadingAvatar()"
/>
</template>

View File

@@ -1,6 +1,4 @@
<script setup lang="ts">
import type { AvatarProps } from '@nuxt/ui'
const searchTerm = ref('')
const searchTermDebounced = refDebounced(searchTerm, 200)
@@ -12,7 +10,7 @@ const { data: users, status } = await useFetch('https://jsonplaceholder.typicode
label: user.name,
value: String(user.id),
avatar: { src: `https://i.pravatar.cc/120?img=${user.id}` }
}))
})) || []
},
lazy: true
})
@@ -21,7 +19,7 @@ const { data: users, status } = await useFetch('https://jsonplaceholder.typicode
<template>
<USelectMenu
v-model:search-term="searchTerm"
:items="users"
:items="users || []"
:loading="status === 'pending'"
ignore-filter
icon="i-lucide-user"
@@ -32,7 +30,7 @@ const { data: users, status } = await useFetch('https://jsonplaceholder.typicode
<UAvatar
v-if="modelValue"
v-bind="modelValue.avatar"
:size="(ui.leadingAvatarSize() as AvatarProps['size'])"
:size="ui.leadingAvatarSize()"
:class="ui.leadingAvatar()"
/>
</template>

View File

@@ -1,6 +1,4 @@
<script setup lang="ts">
import type { SelectMenuItem } from '@nuxt/ui'
const items = ref([
{
label: 'benjamincanac',
@@ -25,16 +23,8 @@ const items = ref([
src: 'https://github.com/noook.png',
alt: 'noook'
}
},
{
label: 'sandros94',
value: 'sandros94',
avatar: {
src: 'https://github.com/sandros94.png',
alt: 'sandros94'
}
}
] satisfies SelectMenuItem[])
])
const value = ref(items.value[0])
</script>

View File

@@ -1,29 +1,27 @@
<script setup lang="ts">
import type { SelectMenuItem, ChipProps } from '@nuxt/ui'
const items = ref([
{
label: 'bug',
value: 'bug',
chip: {
color: 'error'
color: 'error' as const
}
},
{
label: 'feature',
value: 'feature',
chip: {
color: 'success'
color: 'success' as const
}
},
{
label: 'enhancement',
value: 'enhancement',
chip: {
color: 'info'
color: 'info' as const
}
}
] satisfies SelectMenuItem[])
])
const value = ref(items.value[0])
</script>
@@ -35,7 +33,7 @@ const value = ref(items.value[0])
v-bind="modelValue.chip"
inset
standalone
:size="(ui.itemLeadingChipSize() as ChipProps['size'])"
:size="ui.itemLeadingChipSize()"
:class="ui.itemLeadingChip()"
/>
</template>

View File

@@ -1,6 +1,4 @@
<script setup lang="ts">
import type { SelectMenuItem } from '@nuxt/ui'
const items = ref([
{
label: 'Backlog',
@@ -22,7 +20,7 @@ const items = ref([
value: 'done',
icon: 'i-lucide-circle-check'
}
] satisfies SelectMenuItem[])
])
const value = ref(items.value[0])
</script>

View File

@@ -1,6 +1,4 @@
<script setup lang="ts">
import type { AvatarProps } from '@nuxt/ui'
const { data: users, status } = await useFetch('https://jsonplaceholder.typicode.com/users', {
key: 'typicode-users',
transform: (data: { id: number, name: string }[]) => {
@@ -8,7 +6,7 @@ const { data: users, status } = await useFetch('https://jsonplaceholder.typicode
label: user.name,
value: String(user.id),
avatar: { src: `https://i.pravatar.cc/120?img=${user.id}` }
}))
})) || []
},
lazy: true
})
@@ -20,18 +18,17 @@ function getUserAvatar(value: string) {
<template>
<USelect
:items="users"
:items="users || []"
:loading="status === 'pending'"
icon="i-lucide-user"
placeholder="Select user"
class="w-48"
value-key="value"
>
<template #leading="{ modelValue, ui }">
<UAvatar
v-if="modelValue"
v-bind="getUserAvatar(modelValue)"
:size="(ui.leadingAvatarSize() as AvatarProps['size'])"
v-bind="getUserAvatar(modelValue as string)"
:size="ui.leadingAvatarSize()"
:class="ui.leadingAvatar()"
/>
</template>

View File

@@ -1,6 +1,4 @@
<script setup lang="ts">
import type { SelectItem } from '@nuxt/ui'
const items = ref([
{
label: 'benjamincanac',
@@ -25,21 +23,13 @@ const items = ref([
src: 'https://github.com/noook.png',
alt: 'noook'
}
},
{
label: 'sandros94',
value: 'sandros94',
avatar: {
src: 'https://github.com/sandros94.png',
alt: 'sandros94'
}
}
] satisfies SelectItem[])
])
const value = ref(items.value[0]?.value)
const avatar = computed(() => items.value.find(item => item.value === value.value)?.avatar)
</script>
<template>
<USelect v-model="value" :items="items" value-key="value" :avatar="avatar" class="w-48" />
<USelect v-model="value" :avatar="avatar" :items="items" class="w-48" />
</template>

View File

@@ -1,30 +1,27 @@
<script setup lang="ts">
import type { SelectItem, ChipProps } from '@nuxt/ui'
const items = ref([
{
label: 'bug',
value: 'bug',
chip: {
color: 'error'
color: 'error' as const
}
},
{
label: 'feature',
value: 'feature',
chip: {
color: 'success'
color: 'success' as const
}
},
{
label: 'enhancement',
value: 'enhancement',
chip: {
color: 'info'
color: 'info' as const
}
}
] satisfies SelectItem[])
])
const value = ref(items.value[0]?.value)
function getChip(value: string) {
@@ -33,14 +30,14 @@ function getChip(value: string) {
</script>
<template>
<USelect v-model="value" :items="items" value-key="value" class="w-48">
<USelect v-model="value" :items="items" class="w-48">
<template #leading="{ modelValue, ui }">
<UChip
v-if="modelValue"
v-bind="getChip(modelValue)"
v-bind="getChip(modelValue as string)"
inset
standalone
:size="(ui.itemLeadingChipSize() as ChipProps['size'])"
:size="ui.itemLeadingChipSize()"
:class="ui.itemLeadingChip()"
/>
</template>

View File

@@ -1,6 +1,4 @@
<script setup lang="ts">
import type { SelectItem } from '@nuxt/ui'
const items = ref([
{
label: 'Backlog',
@@ -22,12 +20,12 @@ const items = ref([
value: 'done',
icon: 'i-lucide-circle-check'
}
] satisfies SelectItem[])
])
const value = ref(items.value[0]?.value)
const icon = computed(() => items.value.find(item => item.value === value.value)?.icon)
</script>
<template>
<USelect v-model="value" :items="items" value-key="value" :icon="icon" class="w-48" />
<USelect v-model="value" :icon="icon" :items="items" class="w-48" />
</template>

View File

@@ -1,7 +1,5 @@
<script setup lang="ts">
import type { StepperItem } from '@nuxt/ui'
const items: StepperItem[] = [
const items = [
{
title: 'Address',
description: 'Add your address here',

View File

@@ -1,7 +1,5 @@
<script setup lang="ts">
import type { StepperItem } from '@nuxt/ui'
const items: StepperItem[] = [
const items = [
{
slot: 'address',
title: 'Address',

View File

@@ -1,8 +1,7 @@
<script setup lang="ts">
import type { StepperItem } from '@nuxt/ui'
import { onMounted, ref } from 'vue'
const items: StepperItem[] = [
const items = [
{
title: 'Address',
description: 'Add your address here',

View File

@@ -1,7 +1,5 @@
<script setup lang="ts">
import type { StepperItem } from '@nuxt/ui'
const items: StepperItem[] = [
const items = [
{
slot: 'address',
title: 'Address',

View File

@@ -97,11 +97,10 @@ function getHeader(column: Column<Payment>, label: string) {
const isSorted = column.getIsSorted()
return h(UDropdownMenu, {
'content': {
content: {
align: 'start'
},
'aria-label': 'Actions dropdown',
'items': [{
items: [{
label: 'Asc',
type: 'checkbox',
icon: 'i-lucide-arrow-up-narrow-wide',
@@ -127,12 +126,11 @@ function getHeader(column: Column<Payment>, label: string) {
}
}]
}, () => h(UButton, {
'color': 'neutral',
'variant': 'ghost',
color: 'neutral',
variant: 'ghost',
label,
'icon': isSorted ? (isSorted === 'asc' ? 'i-lucide-arrow-up-narrow-wide' : 'i-lucide-arrow-down-wide-narrow') : 'i-lucide-arrow-up-down',
'class': '-mx-2.5 data-[state=open]:bg-(--ui-bg-elevated)',
'aria-label': `Sort by ${isSorted === 'asc' ? 'descending' : 'ascending'}`
icon: isSorted ? (isSorted === 'asc' ? 'i-lucide-arrow-up-narrow-wide' : 'i-lucide-arrow-down-wide-narrow') : 'i-lucide-arrow-up-down',
class: '-mx-2.5 data-[state=open]:bg-(--ui-bg-elevated)'
}))
}

View File

@@ -145,12 +145,12 @@ const columns: TableColumn<Payment>[] = [{
header: ({ table }) => h(UCheckbox, {
'modelValue': table.getIsSomePageRowsSelected() ? 'indeterminate' : table.getIsAllPageRowsSelected(),
'onUpdate:modelValue': (value: boolean | 'indeterminate') => table.toggleAllPageRowsSelected(!!value),
'aria-label': 'Select all'
'ariaLabel': 'Select all'
}),
cell: ({ row }) => h(UCheckbox, {
'modelValue': row.getIsSelected(),
'onUpdate:modelValue': (value: boolean | 'indeterminate') => row.toggleSelected(!!value),
'aria-label': 'Select row'
'ariaLabel': 'Select row'
}),
enableSorting: false,
enableHiding: false
@@ -242,17 +242,15 @@ const columns: TableColumn<Payment>[] = [{
}]
return h('div', { class: 'text-right' }, h(UDropdownMenu, {
'content': {
content: {
align: 'end'
},
items,
'aria-label': 'Actions dropdown'
items
}, () => h(UButton, {
'icon': 'i-lucide-ellipsis-vertical',
'color': 'neutral',
'variant': 'ghost',
'class': 'ml-auto',
'aria-label': 'Actions dropdown'
icon: 'i-lucide-ellipsis-vertical',
color: 'neutral',
variant: 'ghost',
class: 'ml-auto'
})))
}
}]
@@ -296,7 +294,6 @@ function randomize() {
variant="outline"
trailing-icon="i-lucide-chevron-down"
class="ml-auto"
aria-label="Columns select dropdown"
/>
</UDropdownMenu>
</div>

View File

@@ -17,7 +17,7 @@ const { data, status } = await useFetch<User[]>('https://jsonplaceholder.typicod
transform: (data) => {
return data?.map(user => ({
...user,
avatar: { src: `https://i.pravatar.cc/120?img=${user.id}`, alt: `${user.name} avatar` }
avatar: { src: `https://i.pravatar.cc/120?img=${user.id}` }
})) || []
},
lazy: true

View File

@@ -97,17 +97,15 @@ const columns: TableColumn<Payment>[] = [{
id: 'actions',
cell: ({ row }) => {
return h('div', { class: 'text-right' }, h(UDropdownMenu, {
'content': {
content: {
align: 'end'
},
'items': getRowItems(row),
'aria-label': 'Actions dropdown'
items: getRowItems(row)
}, () => h(UButton, {
'icon': 'i-lucide-ellipsis-vertical',
'color': 'neutral',
'variant': 'ghost',
'class': 'ml-auto',
'aria-label': 'Actions dropdown'
icon: 'i-lucide-ellipsis-vertical',
color: 'neutral',
variant: 'ghost',
class: 'ml-auto'
})))
}
}]

View File

@@ -48,15 +48,14 @@ const data = ref<Payment[]>([{
const columns: TableColumn<Payment>[] = [{
id: 'expand',
cell: ({ row }) => h(UButton, {
'color': 'neutral',
'variant': 'ghost',
'icon': 'i-lucide-chevron-down',
'square': true,
'aria-label': 'Expand',
'ui': {
color: 'neutral',
variant: 'ghost',
icon: 'i-lucide-chevron-down',
square: true,
ui: {
leadingIcon: ['transition-transform', row.getIsExpanded() ? 'duration-200 rotate-180' : '']
},
'onClick': () => row.toggleExpanded()
onClick: () => row.toggleExpanded()
})
}, {
accessorKey: 'id',

View File

@@ -50,12 +50,12 @@ const columns: TableColumn<Payment>[] = [{
header: ({ table }) => h(UCheckbox, {
'modelValue': table.getIsSomePageRowsSelected() ? 'indeterminate' : table.getIsAllPageRowsSelected(),
'onUpdate:modelValue': (value: boolean | 'indeterminate') => table.toggleAllPageRowsSelected(!!value),
'aria-label': 'Select all'
'ariaLabel': 'Select all'
}),
cell: ({ row }) => h(UCheckbox, {
'modelValue': row.getIsSelected(),
'onUpdate:modelValue': (value: boolean | 'indeterminate') => row.toggleSelected(!!value),
'aria-label': 'Select row'
'ariaLabel': 'Select row'
})
}, {
accessorKey: 'date',

View File

@@ -50,12 +50,12 @@ const columns: TableColumn<Payment>[] = [{
header: ({ table }) => h(UCheckbox, {
'modelValue': table.getIsSomePageRowsSelected() ? 'indeterminate' : table.getIsAllPageRowsSelected(),
'onUpdate:modelValue': (value: boolean | 'indeterminate') => table.toggleAllPageRowsSelected(!!value),
'aria-label': 'Select all'
'ariaLabel': 'Select all'
}),
cell: ({ row }) => h(UCheckbox, {
'modelValue': row.getIsSelected(),
'onUpdate:modelValue': (value: boolean | 'indeterminate') => row.toggleSelected(!!value),
'aria-label': 'Select row'
'ariaLabel': 'Select row'
})
}, {
accessorKey: 'date',

View File

@@ -95,7 +95,7 @@ function getDropdownActions(user: User): DropdownMenuItem[][] {
<UTable :data="data" :columns="columns" class="flex-1">
<template #name-cell="{ row }">
<div class="flex items-center gap-3">
<UAvatar :src="`https://i.pravatar.cc/120?img=${row.original.id}`" size="lg" :alt="`${row.original.name} avatar`" />
<UAvatar :src="`https://i.pravatar.cc/120?img=${row.original.id}`" size="lg" />
<div>
<p class="font-medium text-(--ui-text-highlighted)">
{{ row.original.name }}
@@ -108,7 +108,7 @@ function getDropdownActions(user: User): DropdownMenuItem[][] {
</template>
<template #action-cell="{ row }">
<UDropdownMenu :items="getDropdownActions(row.original)">
<UButton icon="i-lucide-ellipsis-vertical" color="neutral" variant="ghost" aria-label="Actions" />
<UButton icon="i-lucide-ellipsis-vertical" color="neutral" variant="ghost" />
</UDropdownMenu>
</template>
</UTable>

View File

@@ -1,7 +1,5 @@
<script setup lang="ts">
import type { TabsItem } from '@nuxt/ui'
const items: TabsItem[] = [
const items = [
{
label: 'Account',
icon: 'i-lucide-user'

View File

@@ -1,20 +1,18 @@
<script setup lang="ts">
import type { TabsItem } from '@nuxt/ui'
const items = [
{
label: 'Account',
description: 'Make changes to your account here. Click save when you\'re done.',
icon: 'i-lucide-user',
slot: 'account' as const
slot: 'account'
},
{
label: 'Password',
description: 'Change your password here. After saving, you\'ll be logged out.',
icon: 'i-lucide-lock',
slot: 'password' as const
slot: 'password'
}
] satisfies TabsItem[]
]
const state = reactive({
name: 'Benjamin Canac',

View File

@@ -1,7 +1,5 @@
<script setup lang="ts">
import type { TabsItem } from '@nuxt/ui'
const items: TabsItem[] = [
const items = [
{
label: 'Account'
},

View File

@@ -1,10 +1,10 @@
<script setup lang="ts">
import type { TreeItem } from '@nuxt/ui'
const items = [
const items: TreeItem[] = [
{
label: 'app/',
slot: 'app' as const,
slot: 'app',
defaultExpanded: true,
children: [{
label: 'composables/',
@@ -24,7 +24,7 @@ const items = [
},
{ label: 'app.vue', icon: 'i-vscode-icons-file-type-vue' },
{ label: 'nuxt.config.ts', icon: 'i-vscode-icons-file-type-nuxt' }
] satisfies TreeItem[]
]
</script>
<template>

View File

@@ -25,7 +25,7 @@ const items: TreeItem[] = [
{ label: 'nuxt.config.ts', icon: 'i-vscode-icons-file-type-nuxt' }
]
const value = ref()
const value = ref(items[items.length - 1])
</script>
<template>

View File

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

View File

@@ -3,8 +3,8 @@ withDefaults(defineProps<{
title: string
description: string
headline: string
framework?: string
module?: string
framework: string
module: string
}>(), {
framework: 'nuxt',
module: ''

View File

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

View File

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

View File

@@ -84,10 +84,10 @@ export function useLinks() {
label: 'Community',
icon: 'i-lucide-users',
children: [{
icon: 'i-lucide-presentation',
label: 'Showcase',
description: 'Check out some amazing projects built with Nuxt UI.',
to: '/showcase'
label: 'Roadmap',
description: 'Track our development progress in real-time.',
icon: 'i-lucide-map',
to: '/roadmap'
}, {
label: 'Devtools Integration',
description: 'Integrate Nuxt UI with Nuxt Devtools with Compodium.',
@@ -112,5 +112,5 @@ export function useLinks() {
icon: 'i-lucide-rocket',
to: 'https://github.com/nuxt/ui/releases',
target: '_blank'
}])
}].filter(Boolean))
}

View File

@@ -1,66 +0,0 @@
export function useSearchLinks() {
return [{
label: 'Docs',
icon: 'i-lucide-square-play',
to: '/getting-started'
}, {
label: 'Components',
icon: 'i-lucide-square-code',
to: '/components'
}, {
icon: 'i-lucide-sparkles',
label: 'Pro > Features',
description: 'A collection of premium Vue components.',
to: '/pro'
}, {
icon: 'i-lucide-credit-card',
label: 'Pro > Pricing',
description: 'Free in development, buy when ready to launch.',
to: '/pro/pricing'
}, {
icon: 'i-lucide-panels-top-left',
label: 'Pro > Templates',
description: 'Official templates made with Nuxt UI Pro.',
to: '/pro/templates'
}, {
icon: 'i-lucide-circle-check',
label: 'Pro > Activate',
description: 'Enable Nuxt UI Pro in your production projects.',
to: '/pro/activate'
}, {
label: 'Figma',
icon: 'i-simple-icons-figma',
to: '/figma'
}, {
icon: 'i-lucide-presentation',
label: 'Community > Showcase',
description: 'Check out some of the amazing projects built with Nuxt UI.',
to: '/showcase'
}, {
label: 'Community > Contribution',
description: 'A comprehensive guide on contributing to Nuxt UI, including project structure, development workflow, and best practices.',
icon: 'i-lucide-git-pull-request-arrow',
to: '/getting-started/contribution'
}, {
label: 'Community > Roadmap',
description: 'Track our development progress in real-time.',
icon: 'i-lucide-map',
to: '/roadmap'
}, {
label: 'Community > Devtools',
description: 'Integrate Nuxt UI with Nuxt Devtools with Compodium.',
icon: 'i-lucide-code',
to: 'https://github.com/romhml/compodium',
target: '_blank'
}, {
label: 'Community > Team',
description: 'Meet the team behind Nuxt UI.',
icon: 'i-lucide-users',
to: '/team'
}, {
label: 'Releases',
icon: 'i-lucide-rocket',
to: 'https://github.com/nuxt/ui/releases',
target: '_blank'
}]
}

View File

@@ -1,5 +1,6 @@
<script setup lang="ts">
import colors from 'tailwindcss/colors'
// import { debounce } from 'perfect-debounce'
import type { NuxtError } from '#app'
const props = defineProps<{
@@ -14,8 +15,17 @@ const { data: files } = useLazyAsyncData('search', () => queryCollectionSearchSe
server: false
})
const searchTerm = ref('')
// watch(searchTerm, debounce((query: string) => {
// if (!query) {
// return
// }
// useTrackEvent('Search', { props: { query: `${query} - ${searchTerm.value?.commandPaletteRef.results.length} results` } })
// }, 500))
const links = useLinks()
const searchLinks = useSearchLinks()
const color = computed(() => colorMode.value === 'dark' ? (colors as any)[appConfig.ui.colors.neutral][900] : 'white')
const radius = computed(() => `:root { --ui-radius: ${appConfig.theme.radius}rem; }`)
const blackAsPrimary = computed(() => appConfig.theme.blackAsPrimary ? `:root { --ui-primary: black; } .dark { --ui-primary: white; }` : ':root {}')
@@ -38,7 +48,7 @@ useHead({
})
useSeoMeta({
titleTemplate: '%s - Nuxt UI',
titleTemplate: '%s - Nuxt UI v3',
title: String(props.error.statusCode)
})
@@ -57,17 +67,17 @@ provide('navigation', mappedNavigation)
<UApp>
<NuxtLoadingIndicator color="#FFF" />
<Banner />
<!-- <Banner /> -->
<Header :links="links" />
<UError :error="error" />
<Footer />
<!-- <Footer /> -->
<ClientOnly>
<LazyUContentSearch
:links="searchLinks"
v-model:search-term="searchTerm"
:files="files"
:groups="[{
id: 'framework',

View File

@@ -97,7 +97,7 @@ design_system:
```
```css [main.css]
@import "tailwindcss";
@import "tailwindcss" theme(static);
@import "@nuxt/ui";
:root {
@@ -176,11 +176,7 @@ community:
links:
- label: Star on GitHub
color: neutral
variant: outline
to: https://github.com/nuxt/ui
target: _blank
icon: i-lucide-star
- label: Meet the team
color: neutral
variant: outline
to: /team
trailingIcon: i-lucide-arrow-right

View File

@@ -1,8 +1,7 @@
<script setup lang="ts">
import { kebabCase } from 'scule'
import type { ContentNavigationItem } from '@nuxt/content'
import type { PageLink } from '@nuxt/ui-pro'
import { findPageBreadcrumb, mapContentNavigation } from '@nuxt/ui-pro/utils/content'
import { findPageBreadcrumb, mapContentNavigation } from '#ui-pro/utils/content'
const route = useRoute()
const { framework, module } = useSharedData()
@@ -67,9 +66,9 @@ if (!import.meta.prerender) {
const type = page.value?.path.includes('components') ? 'Vue Component ' : page.value?.path.includes('composables') ? 'Vue Composable ' : ''
useSeoMeta({
titleTemplate: `%s ${type}- Nuxt UI ${page.value.module === 'ui-pro' ? 'Pro' : ''} ${page.value.framework === 'vue' ? ' for Vue' : ''}`,
titleTemplate: `%s ${type}- Nuxt UI ${page.value.module === 'ui-pro' ? 'Pro' : ''} v3${page.value.framework === 'vue' ? ' for Vue' : ''}`,
title: page.value.navigation?.title ? page.value.navigation.title : page.value.title,
ogTitle: `${page.value.navigation?.title ? page.value.navigation.title : page.value.title} ${type}- Nuxt UI ${page.value.module === 'ui-pro' ? 'Pro' : ''} ${page.value.framework === 'vue' ? ' for Vue' : ''}`,
ogTitle: `${page.value.navigation?.title ? page.value.navigation.title : page.value.title} ${type}- Nuxt UI ${page.value.module === 'ui-pro' ? 'Pro' : ''} v3${page.value.framework === 'vue' ? ' for Vue' : ''}`,
description: page.value.description,
ogDescription: page.value.description
})
@@ -83,8 +82,6 @@ if (route.path.startsWith('/components')) {
})
} else {
defineOgImageComponent('Docs', {
title: page.value.title,
description: page.value.description,
headline: breadcrumb.value?.[breadcrumb.value.length - 1]?.label || 'Nuxt UI',
framework: page.value?.framework,
module: page.value.module
@@ -101,25 +98,32 @@ const communityLinks = computed(() => [{
label: 'Star on GitHub',
to: `https://github.com/nuxt/${page.value?.module === 'ui-pro' ? 'ui-pro' : 'ui'}`,
target: '_blank'
}, module.value === 'ui-pro' && {
icon: 'i-lucide-credit-card',
label: 'Purchase a license',
to: 'https://nuxt.lemonsqueezy.com/checkout/buy/057dacb2-87ba-4dc1-9256-59ee5b3bd394',
target: '_blank'
}, module.value === 'ui-pro' && {
icon: 'i-lucide-ticket-percent',
label: 'Become an affiliate',
to: 'https://nuxt.lemonsqueezy.com/affiliates',
target: '_blank'
}, {
icon: 'i-lucide-git-pull-request-arrow',
icon: 'i-lucide-life-buoy',
label: 'Contribution',
to: '/getting-started/contribution'
}, {
label: 'Roadmap',
icon: 'i-lucide-map',
to: '/roadmap'
}].filter(Boolean) as PageLink[])
}])
// const resourcesLinks = [{
// icon: 'i-simple-icons-figma',
// label: 'Figma Kit',
// to: 'https://www.figma.com/community/file/1288455405058138934',
// target: '_blank'
// }, {
// label: 'Playground',
// icon: 'i-simple-icons-stackblitz',
// to: 'https://stackblitz.com/edit/nuxt-ui',
// target: '_blank'
// }, {
// icon: 'i-simple-icons-nuxtdotjs',
// label: 'Nuxt docs',
// to: 'https://nuxt.com',
// target: '_blank'
// }]
</script>
<template>
@@ -147,7 +151,7 @@ const communityLinks = computed(() => [{
v-bind="link"
>
<template v-if="link.avatar" #leading>
<UAvatar v-bind="link.avatar" size="2xs" :alt="`${link.label} avatar`" />
<UAvatar v-bind="link.avatar" size="2xs" />
</template>
</UButton>
</template>
@@ -168,9 +172,14 @@ const communityLinks = computed(() => [{
<UPageLinks title="Community" :links="communityLinks" />
<!-- <USeparator type="dashed" />
<UPageLinks title="Resources" :links="resourcesLinks" />
<USeparator type="dashed" />
<AdsCarbon />
<AdsPro />
<AdsCarbon /> -->
</template>
</UContentToc>
</template>

View File

@@ -7,7 +7,7 @@ const title = 'Vue Components'
const description = 'Explore 99+ customizable UI components for Vue and Nuxt built with Tailwind CSS and Reka UI.'
useSeoMeta({
titleTemplate: '%s - Nuxt UI',
titleTemplate: `%s - Nuxt UI`,
title,
description,
ogTitle: `${title} - Nuxt UI`,
@@ -119,8 +119,7 @@ onMounted(() => {
/>
</template>
<LazyStarsBg />
<StarsBg />
<div aria-hidden="true" class="hidden lg:block absolute z-[-1] border-x border-(--ui-border) inset-0 mx-4 sm:mx-6 lg:mx-8" />
</UPageHero>
@@ -169,7 +168,6 @@ onMounted(() => {
:loading="index >= 4 ? 'lazy' : 'eager'"
width="640"
height="360"
:alt="`${component.name} preview`"
/>
</div>
</UPageCard>

View File

@@ -178,7 +178,6 @@ pricing:
- title: Solo License
description: Design faster with all Nuxt UI Pro components.
price: $149
# discount: $119
billing_period: one-time payment
billing_cycle: plus local taxes
class: bg-(--ui-bg-elevated)/50
@@ -200,7 +199,6 @@ pricing:
- title: Team License
description: Everything you need to deliver faster as a team.
price: $349
# discount: $279
billing_period: one-time payment
billing_cycle: plus local taxes
class: bg-(--ui-bg-elevated)/50
@@ -277,14 +275,16 @@ faq:
content: As the Figma Pro Kit is a digital product packaged as a zip file, we cannot offer refunds once the purchase is made.
- label: Do you have a Figma to Code plugin?
content: >
We recommend the open source [TemPad Dev](https://github.com/ecomfe/tempad-dev) inspect panel with the [TemPad Dev Nuxt UI Plugin](https://github.com/Justineo/tempad-dev-plugin-nuxt-ui):
We recommend the open source [TeamPad Dev](https://github.com/ecomfe/tempad-dev) inspect panel with the [TeamPad Dev Nuxt UI Plugin](https://github.com/Justineo/tempad-dev-plugin-nuxt-ui):
1. Install the [TemPad Dev Chrome Extension](https://chromewebstore.google.com/detail/tempad-dev/lgoeakbaikpkihoiphamaeopmliaimpc)
1. Install the [TeamPad Dev Chrome Extension](https://chromewebstore.google.com/detail/tempad-dev/lgoeakbaikpkihoiphamaeopmliaimpc)
2. Open your Figma file with Nuxt UI components (reload the page if you don't see the TemPad Dev panel)
2. Open your Figma file with Nuxt UI components (reload the page if you don't see the TeamPad Dev panel)
3. Install the `@nuxt` (or `@nuxt/pro` for Nuxt UI Pro) in TemPad Dev's plugins section
3. Install the `@nuxt` in TeamPad Dev's plugins section
4. Select any Nuxt UI component and inspect the code it generates
![TemPad Dev Nuxt UI Plugin](/pro/figma/teampad-dev-nuxt-ui-plugin.gif){.w-full .rounded .mb-2 .max-w-[636px]}
![TeamPad Dev Nuxt UI Plugin](/pro/figma/teampad-dev-nuxt-ui-plugin.gif){.w-full .rounded .mb-2 .max-w-[636px]}
*Right now, only Nuxt UI components are supported, but the code of the plugin is [open source](https://github.com/Justineo/tempad-dev-plugin-nuxt-ui) and anyone can contribute to it.*

View File

@@ -1,11 +1,10 @@
<script setup lang="ts">
// @ts-expect-error yaml is not typed
import page from '.figma.yml'
import { animate } from 'motion-v'
import { animate } from 'motion'
import { joinURL } from 'ufo'
const { url } = useSiteConfig()
useSeoMeta({
title: page.title,
description: page.description,
@@ -156,7 +155,7 @@ onMounted(async () => {
:src="item.src"
:alt="item.alt"
class="w-full h-auto rounded-[calc(var(--ui-radius)*2)]"
loading="lazy"
lazy
/>
</template>
</UTabs>
@@ -166,7 +165,7 @@ onMounted(async () => {
v-if="page.section2.image"
v-bind="page.section2.image"
class="w-full h-auto rounded-[calc(var(--ui-radius)*2)]"
loading="lazy"
lazy
/>
</UPageSection>
<UPageSection v-bind="page.section3" orientation="horizontal" :ui="{ container: 'py-16 sm:pt-16 lg:pt-16' }">
@@ -174,7 +173,7 @@ onMounted(async () => {
v-if="page.section3.image"
v-bind="page.section3.image"
class="w-full h-auto rounded-[calc(var(--ui-radius)*2)]"
loading="lazy"
lazy
/>
</UPageSection>
<USeparator />
@@ -199,7 +198,7 @@ onMounted(async () => {
v-if="step.image"
v-bind="step.image"
class="rounded-(--ui-radius)"
loading="lazy"
lazy
/>
<div>
<h2 class="font-semibold inline-flex items-center gap-x-1">
@@ -234,7 +233,6 @@ onMounted(async () => {
:title="plan.title"
:description="plan.description"
:price="plan.price"
:discount="plan.discount"
:billing-period="plan.billing_period"
:billing-cycle="plan.billing_cycle"
:highlight="plan.highlight"
@@ -273,7 +271,6 @@ onMounted(async () => {
:key="index"
v-bind="logo"
class="h-6 shrink-0 max-w-[140px] filter invert dark:invert-0"
loading="lazy"
>
</UPageMarquee>
</UPageCTA>

View File

@@ -23,7 +23,18 @@ const { data: components } = await useAsyncData('ui-components', () => {
.all()
})
const { data: module } = await useFetch('/api/module.json')
const { data: module } = await useFetch<{
stats: {
downloads: number
stars: number
}
contributors: {
username: string
}[]
}>('https://api.nuxt.com/modules/ui', {
key: 'stats',
transform: ({ stats, contributors }) => ({ stats, contributors })
})
const { format } = Intl.NumberFormat('en', { notation: 'compact' })
@@ -65,7 +76,7 @@ useIntersectionObserver(contributorsRef, ([entry]) => {
:key="feature.title"
as-child
:initial="{ opacity: 0, transform: 'translateX(-10px)' }"
:while-in-view="{ opacity: 1, transform: 'translateX(0)' }"
:in-view="{ opacity: 1, transform: 'translateX(0)' }"
:transition="{ delay: 0.2 + 0.4 * index }"
:in-view-options="{ once: true }"
>
@@ -74,7 +85,7 @@ useIntersectionObserver(contributorsRef, ([entry]) => {
</div>
</template>
<LazySkyBg is-index />
<SkyBg />
<div class="h-[344px] lg:h-full lg:relative w-full lg:min-h-[calc(100vh-var(--ui-header-height)-1px)] overflow-hidden">
<UPageMarquee
@@ -82,7 +93,7 @@ useIntersectionObserver(contributorsRef, ([entry]) => {
:overlay="false"
:ui="{
root: '[--gap:--spacing(4)] [--duration:40s] border-(--ui-border) absolute w-full left-0 border-y lg:border-x lg:border-y-0 lg:w-[calc(50%-6px)] 2xl:w-[320px] lg:flex-col',
content: 'lg:w-auto lg:flex-col lg:animate-[marquee-vertical_var(--duration)_linear_infinite] lg:rtl:animate-[marquee-vertical-rtl_var(--duration)_linear_infinite] lg:h-[fit-content]'
content: 'lg:w-auto lg:h-full lg:flex-col lg:animate-[marquee-vertical_var(--duration)_linear_infinite] lg:rtl:animate-[marquee-vertical-rtl_var(--duration)_linear_infinite] lg:h-[fit-content]'
}"
>
<ULink
@@ -92,14 +103,10 @@ useIntersectionObserver(contributorsRef, ([entry]) => {
:to="component.path"
>
<UColorModeImage
:light="`${component.path.replace('/components/', '/components/light/')}.png`"
:dark="`${component.path.replace('/components/', '/components/dark/')}.png`"
:alt="`${component.title} preview`"
width="290"
height="163"
format="webp"
class="hover:scale-105 lg:hover:scale-110 transition-transform aspect-video w-full border-x lg:border-x-0 lg:border-y border-(--ui-border) 2xl:border-y-0"
loading="lazy"
/>
<UBadge color="neutral" variant="outline" size="md" :label="component.title" class="hidden lg:block absolute mx-auto top-4 left-6 xl:left-4 group-hover/link:opacity-100 opacity-0 transition-all duration-300 pointer-events-none -translate-y-2 group-hover/link:translate-y-0" />
</ULink>
@@ -111,7 +118,7 @@ useIntersectionObserver(contributorsRef, ([entry]) => {
:overlay="false"
:ui="{
root: '[--gap:--spacing(4)] [--duration:40s] border-(--ui-border) absolute w-full mt-[180px] left-0 border-y lg:mt-auto lg:left-auto lg:border-y-0 lg:border-x lg:w-[calc(50%-6px)] 2xl:w-[320px] lg:right-0 lg:flex-col',
content: 'lg:w-auto lg:flex-col lg:animate-[marquee-vertical_var(--duration)_linear_infinite] lg:rtl:animate-[marquee-vertical-rtl_var(--duration)_linear_infinite] lg:h-[fit-content] lg:[animation-direction:reverse]'
content: 'lg:w-auto lg:h-full lg:flex-col lg:animate-[marquee-vertical_var(--duration)_linear_infinite] lg:rtl:animate-[marquee-vertical-rtl_var(--duration)_linear_infinite] lg:h-[fit-content] lg:[animation-direction:reverse]'
}"
>
<ULink
@@ -123,12 +130,7 @@ useIntersectionObserver(contributorsRef, ([entry]) => {
<UColorModeImage
:light="`${component.path.replace('/components/', '/components/light/')}.png`"
:dark="`${component.path.replace('/components/', '/components/dark/')}.png`"
:alt="`${component.title} preview`"
width="290"
height="163"
format="webp"
class="hover:scale-105 lg:hover:scale-110 transition-transform aspect-video w-full border-x lg:border-x-0 lg:border-y border-(--ui-border) 2xl:border-y-0"
loading="lazy"
/>
<UBadge color="neutral" variant="outline" size="md" :label="component.title" class="hidden lg:block absolute mx-auto top-4 left-6 xl:left-4 group-hover/link:opacity-100 opacity-0 transition-all duration-300 pointer-events-none -translate-y-2 group-hover/link:translate-y-0" />
</ULink>
@@ -145,14 +147,12 @@ useIntersectionObserver(contributorsRef, ([entry]) => {
:key="feature.title"
as="li"
:initial="{ opacity: 0, transform: 'translateY(10px)' }"
:while-in-view="{ opacity: 1, transform: 'translateY(0)' }"
:in-view="{ opacity: 1, transform: 'translateY(0)' }"
:transition="{ delay: 0.1 * index }"
:in-view-options="{ once: true }"
class="flex items-start gap-x-3 relative group"
>
<NuxtLink v-if="feature.to" :to="feature.to" class="absolute inset-0 z-10">
<span class="sr-only">Go to {{ feature.title }}</span>
</NuxtLink>
<NuxtLink v-if="feature.to" :to="feature.to" class="absolute inset-0 z-10" />
<div class="relative p-3">
<svg class="absolute inset-0" viewBox="0 0 44 44" fill="none" xmlns="http://www.w3.org/2000/svg">
@@ -165,12 +165,12 @@ useIntersectionObserver(contributorsRef, ([entry]) => {
<circle cx="6.53711" cy="37.4551" r="1.5" fill="var(--ui-border-accented)" />
<circle cx="38.5957" cy="37.4551" r="1.5" fill="var(--ui-border-accented)" />
</svg>
<UIcon :name="feature.icon" class="size-5 shrink-0" />
<UIcon :name="feature.icon" class="size-5 flex-shrink-0" />
</div>
<div class="flex flex-col">
<h2 class="font-medium text-(--ui-text-highlighted) inline-flex items-center gap-x-1">
{{ feature.title }}
<UIcon v-if="feature.to" name="i-lucide-arrow-right" class="size-4 shrink-0 opacity-0 group-hover:opacity-100 transition-all duration-200 -translate-x-1 group-hover:translate-x-0" />
<UIcon v-if="feature.to" name="i-lucide-arrow-right" class="size-4 flex-shrink-0 opacity-0 group-hover:opacity-100 transition-all duration-200 -translate-x-1 group-hover:translate-x-0" />
</h2>
<p class="text-sm text-(--ui-text-muted)">
{{ feature.description }}
@@ -189,7 +189,7 @@ useIntersectionObserver(contributorsRef, ([entry]) => {
:links="page.design_system.links"
orientation="horizontal"
>
<MDC :value="page.design_system.code" cache-key="index-design-system-code" />
<MDC :value="page.design_system.code" />
</UPageSection>
<USeparator />
@@ -201,10 +201,10 @@ useIntersectionObserver(contributorsRef, ([entry]) => {
orientation="horizontal"
>
<template #description>
<MDC :value="page.component_customization.description" cache-key="index-component-customization-description" />
<MDC :value="page.component_customization.description" />
</template>
<MDC :value="page.component_customization.code" cache-key="index-component-customization-code" />
<MDC :value="page.component_customization.code" />
</UPageSection>
<USeparator />
@@ -218,32 +218,26 @@ useIntersectionObserver(contributorsRef, ([entry]) => {
class="border-b border-(--ui-border)"
>
<template #features>
<li>
<NuxtLink to="https://npm.chart.dev/@nuxt/ui" target="_blank" class="min-w-0">
<p class="text-4xl font-semibold text-(--ui-text-highlighted) truncate">
{{ format(module?.stats?.downloads ?? 0) }}+
</p>
<p class="text-(--ui-text-muted) text-sm truncate">monthly downloads</p>
</NuxtLink>
</li>
<NuxtLink to="https://npm.chart.dev/@nuxt/ui" target="_blank" class="min-w-0">
<p class="text-4xl font-semibold text-(--ui-text-highlighted) truncate">
{{ format(module?.stats?.downloads ?? 0) }}+
</p>
<p class="text-(--ui-text-muted) text-sm truncate">monthly downloads</p>
</NuxtLink>
<li>
<NuxtLink to="https://github.com/nuxt/ui" target="_blank" class="min-w-0">
<p class="text-4xl font-semibold text-(--ui-text-highlighted) truncate">
{{ format(module?.stats?.stars ?? 0) }}+
</p>
<p class="text-(--ui-text-muted) text-sm truncate">GitHub stars</p>
</NuxtLink>
</li>
<NuxtLink to="https://github.com/nuxt/ui" target="_blank" class="min-w-0">
<p class="text-4xl font-semibold text-(--ui-text-highlighted) truncate">
{{ format(module?.stats?.stars ?? 0) }}+
</p>
<p class="text-(--ui-text-muted) text-sm truncate">GitHub stars</p>
</NuxtLink>
<li>
<NuxtLink to="https://github.com/nuxt/ui/graphs/contributors" target="_blank" class="min-w-0">
<p class="text-4xl font-semibold text-(--ui-text-highlighted) truncate">
175+
</p>
<p class="text-(--ui-text-muted) text-sm truncate">Contributors</p>
</NuxtLink>
</li>
<NuxtLink to="https://github.com/nuxt/ui/graphs/contributors" target="_blank" class="min-w-0">
<p class="text-4xl font-semibold text-(--ui-text-highlighted) truncate">
175+
</p>
<p class="text-(--ui-text-muted) text-sm truncate">Contributors</p>
</NuxtLink>
</template>
<div ref="contributorsRef" class="p-4 sm:px-6 md:px-8 lg:px-12 xl:px-14 overflow-hidden flex relative">
@@ -267,7 +261,7 @@ useIntersectionObserver(contributorsRef, ([entry]) => {
</UButton>
</template>
<LazyStarsBg />
<StarsBg />
<div aria-hidden="true" class="hidden lg:block absolute z-[-1] border-x border-(--ui-border) inset-0 mx-4 sm:mx-6 lg:mx-8" />
<div class="relative h-[400px] border border-(--ui-border) bg-(--ui-bg-muted) overflow-hidden border-x-0 -mx-4 sm:-mx-6 lg:mx-0 lg:border-x w-screen lg:w-full">
@@ -302,8 +296,8 @@ useIntersectionObserver(contributorsRef, ([entry]) => {
:src="`/pro/blocks/image${i}.png`"
width="460"
height="258"
loading="lazy"
:alt="`Nuxt UI Pro Screenshot ${i}`"
loading="lazy"
class="aspect-video border border-(--ui-border) rounded-[calc(var(--ui-radius)*2)] bg-white"
>
</UPageMarquee>

View File

@@ -17,10 +17,7 @@ pricing:
title: Figma Kit Pro
description: Get all Nuxt UI Pro components in a Figma kit to design your next application before coding. Everything you need, from wire-framing to high-fidelity web integration.
orientation: horizontal
price: $149
# discount: $119
billing_period: one-time payment
billing_cycle: plus local taxes
price: $149 - $349
terms: Solo & Team licenses available.
features:
- 1700+ components & variants from Nuxt UI & UI Pro
@@ -39,7 +36,6 @@ pricing:
- title: Solo
description: Tailored for indie hackers, freelancers and solo founders.
price: $249
# discount: $199
billing_period: one-time payment
billing_cycle: plus local taxes
features:
@@ -54,7 +50,6 @@ pricing:
- title: Startup
description: Best suited for small teams, startups and agencies.
price: $499
# discount: $399
billing_period: one-time payment
billing_cycle: plus local taxes
features:
@@ -70,7 +65,6 @@ pricing:
- title: Organization
description: Ideal for larger teams and organizations.
price: $999
# discount: $799
billing_period: one-time payment
billing_cycle: plus local taxes
features:
@@ -180,7 +174,7 @@ testimonials:
- quote: "Nuxt UI Pro is my preferred choice for everything, from a POC to a web platform. It's ready to use out-of-the-box and assists me in crafting pixel-perfect UIs. It saves me a significant amount of time while remaining highly customizable. Give it a try, and you won't be let down."
user:
name: 'Estéban Soubiran'
description: 'Software engineer'
description: 'Web developer and UnJS member'
to: 'https://x.com/soubiran_'
target: _blank
avatar:

View File

@@ -12,7 +12,7 @@ const { url } = useSiteConfig()
useSeoMeta({
title,
description,
ogTitle: title,
ogTitle: `${title} - Nuxt UI Pro`,
ogDescription: description,
ogImage: joinURL(url, '/pro/og-image.png')
})
@@ -71,8 +71,7 @@ onMounted(() => {
<template>
<UMain>
<UPageHero headline="License Activation" :title="title" :description="description" :ui="{ container: 'relative overflow-hidden', wrapper: 'lg:px-12', description: 'text-pretty' }">
<LazyStarsBg />
<StarsBg />
<div aria-hidden="true" class="hidden lg:block absolute z-[-1] border-x border-(--ui-border) inset-0 mx-4 sm:mx-6 lg:mx-8" />
<div class="px-4 py-10 lg:border border-(--ui-border) bg-(--ui-bg)">

View File

@@ -9,10 +9,10 @@ const { url } = useSiteConfig()
useSeoMeta({
title: page.title,
description: page.description,
ogTitle: page.title,
ogDescription: page.description,
ogImage: joinURL(url, '/pro/og-image.png')
ogImage: joinURL(url, '/pro/og-image.png'),
description: page.description,
ogDescription: page.description
})
</script>
@@ -32,7 +32,7 @@ useSeoMeta({
<MDC :value="page.hero.description" tag="span" unwrap="p" cache-key="pro-hero-description" />
</template>
<LazyStarsBg />
<StarsBg />
<Motion as-child :initial="{ height: 0 }" :animate="{ height: 'auto' }" :transition="{ delay: 0.2, duration: 1 }">
<div aria-hidden="true" class="hidden lg:block absolute z-[-1] border-x border-(--ui-border) inset-0 mx-4 sm:mx-6 lg:mx-8" />
@@ -82,11 +82,11 @@ useSeoMeta({
}"
>
<template #description>
<Motion :initial="{ opacity: 0, transform: 'translateY(10px)' }" :while-in-view="{ opacity: 1, transform: 'translateY(0)' }" :in-view-options="{ once: true }" :transition="{ delay: 0.2 }">
<Motion :initial="{ opacity: 0, transform: 'translateY(10px)' }" :in-view="{ opacity: 1, transform: 'translateY(0)' }" :in-view-options="{ once: true }" :transition="{ delay: 0.2 }">
<MDC :value="page.testimonial.quote" tag="span" unwrap="p" class="before:content-[open-quote] after:content-[close-quote]" cache-key="pro-testimonial-quote" />
</Motion>
</template>
<Motion :initial="{ opacity: 0, transform: 'translateY(10px)' }" :while-in-view="{ opacity: 1, transform: 'translateY(0)' }" :in-view-options="{ once: true }" :transition="{ delay: 0.3 }">
<Motion :initial="{ opacity: 0, transform: 'translateY(10px)' }" :in-view="{ opacity: 1, transform: 'translateY(0)' }" :in-view-options="{ once: true }" :transition="{ delay: 0.3 }">
<UUser
v-bind="page.testimonial.user"
class="justify-center"
@@ -103,7 +103,7 @@ useSeoMeta({
}"
class="border-t border-(--ui-border)"
>
<Motion as-child :initial="{ height: 0 }" :while-in-view="{ height: 'auto' }" :transition="{ delay: 0.4, duration: 1 }">
<Motion as-child :initial="{ height: 0 }" :in-view="{ height: 'auto' }" :transition="{ delay: 0.4, duration: 1 }">
<div aria-hidden="true" class="hidden lg:block absolute z-[-1] border-x border-(--ui-border) inset-0 mx-4 sm:mx-6 lg:mx-8" />
</Motion>
</UPageSection>
@@ -196,7 +196,7 @@ useSeoMeta({
class="overflow-hidden"
orientation="horizontal"
>
<LazyStarsBg />
<StarsBg />
<video
class="rounded-[var(--ui-radius)] z-10"

View File

@@ -7,8 +7,8 @@ const { url } = useSiteConfig()
useSeoMeta({
title: page.title,
description: page.description,
ogTitle: page.title,
description: page.description,
ogDescription: page.description,
ogImage: joinURL(url, '/pro/og-image.png')
})
@@ -27,10 +27,8 @@ useSeoMeta({
<MDC :value="page.pricing.title" unwrap="p" cache-key="pro-pricing-title" />
</template>
<LazyStarsBg />
<StarsBg />
<div aria-hidden="true" class="hidden lg:block absolute z-[-1] border-x border-(--ui-border) inset-0 mx-4 sm:mx-6 lg:mx-8" />
<div class="flex flex-col bg-(--ui-bg) gap-8 lg:gap-0">
<UPricingPlan
v-bind="page.pricing.freePlan"
@@ -44,7 +42,6 @@ useSeoMeta({
:title="plan.title"
:description="plan.description"
:price="plan.price"
:discount="plan.discount"
:billing-period="plan.billing_period"
:billing-cycle="plan.billing_cycle"
:variant="plan.highlight ? 'soft' : 'outline'"
@@ -56,8 +53,6 @@ useSeoMeta({
<UPricingPlan
v-bind="page.pricing.figma"
variant="naked"
:billing-period="page.pricing.figma.billing_period"
:billing-cycle="page.pricing.figma.billing_cycle"
class="lg:rounded-none border lg:border-y-0 border-(--ui-border)"
>
<template #features>
@@ -81,7 +76,6 @@ useSeoMeta({
:key="index"
v-bind="logo"
class="h-6 shrink-0 max-w-[140px] filter invert dark:invert-0"
loading="lazy"
>
</UPageMarquee>
<UContainer>

View File

@@ -18,8 +18,7 @@ useSeoMeta({
<template>
<div class="relative">
<UPageHero :links="page.links" :ui="{ container: 'relative' }">
<LazyStarsBg />
<StarsBg />
<div aria-hidden="true" class="hidden lg:block absolute z-[-1] border-x border-(--ui-border) inset-0 mx-4 sm:mx-6 lg:mx-8" />
<template #title>
@@ -51,12 +50,11 @@ useSeoMeta({
</template>
<div class="lg:border-x border-(--ui-border) h-full flex items-center lg:bg-(--ui-bg-muted)/20">
<Motion class="flex-1" :initial="{ opacity: 0, transform: 'translateY(10px)' }" :while-in-view="{ opacity: 1, transform: 'translateY(0px)' }" :in-view-options="{ once: true }" :transition="{ duration: 0.5, delay: 0.2 }">
<Motion class="flex-1" :initial="{ opacity: 0, transform: 'translateY(10px)' }" :in-view="{ opacity: 1, transform: 'translateY(0px)' }" :in-view-options="{ once: true }" :transition="{ duration: 0.5, delay: 0.2 }">
<UColorModeImage
v-if="template.thumbnail"
v-bind="template.thumbnail"
class="w-full h-auto border lg:border-y lg:border-x-0 border-(--ui-border) rounded-(--ui-radius) lg:rounded-none"
:alt="`Template ${index} thumbnail`"
width="656"
height="369"
loading="lazy"
@@ -67,7 +65,7 @@ useSeoMeta({
:items="(template.images as any[])"
dots
>
<NuxtImg v-bind="item" class="w-full h-full object-cover" width="576" height="360" loading="lazy" />
<NuxtImg v-bind="item" class="w-full h-full object-cover" width="576" height="360" />
</UCarousel>
<Placeholder v-else class="w-full h-full aspect-video" />
</Motion>

View File

@@ -13,7 +13,7 @@ const description = page.value.description
useSeoMeta({
title,
description,
ogTitle: title,
ogTitle: `${title} - Nuxt UI Pro`,
ogDescription: description,
ogImage: joinURL(url, '/pro/og-image.png')
})

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