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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -50,164 +50,16 @@ jobs:
run: pnpm run typecheck run: pnpm run typecheck
- name: Test - name: Test
run: pnpm run test run run: pnpm run test
- name: Test (vue) - name: Test (vue)
run: pnpm run test:vue run run: pnpm run test:vue
- name: Build - name: Build
run: pnpm run build run: pnpm run build
- name: Build playground - name: Build vue fixture
run: pnpm run dev:build run: pnpm run test:vue:build
- name: Build playground (vue)
run: pnpm run dev:vue:build
- name: Publish - name: Publish
run: pnpx pkg-pr-new publish --compact --no-template --pnpm run: pnpx pkg-pr-new publish --compact --no-template --pnpm
starter-nuxt:
needs: build
runs-on: ${{ matrix.os }}
permissions:
contents: read
pull-requests: read
strategy:
matrix:
os: [ubuntu-latest] # macos-latest, windows-latest
node: [22]
steps:
- name: Checkout
uses: actions/checkout@v4
with:
repository: nuxtlabs/nuxt-ui-starter
- name: Store commit SHA
run: |
echo "COMMIT_SHA=$(echo ${{ github.workflow_sha }} | cut -c1-7)" >> $GITHUB_ENV
- name: Install pnpm
uses: pnpm/action-setup@v4
- name: Install node
uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- name: Install latest nuxt/ui
run: pnpm install https://pkg.pr.new/@nuxt/ui@${{ env.COMMIT_SHA }} --lockfile-only
- name: Install dependencies
run: pnpm install
- name: Typecheck
run: pnpm run typecheck
- name: Build
run: pnpm run build
starter-vue:
needs: build
runs-on: ${{ matrix.os }}
permissions:
contents: read
pull-requests: read
strategy:
matrix:
os: [ubuntu-latest] # macos-latest, windows-latest
node: [22]
steps:
- name: Checkout
uses: actions/checkout@v4
with:
repository: nuxtlabs/nuxt-ui-vue-starter
- name: Store commit SHA
run: |
echo "COMMIT_SHA=$(echo ${{ github.workflow_sha }} | cut -c1-7)" >> $GITHUB_ENV
- name: Install pnpm
uses: pnpm/action-setup@v4
- name: Install node
uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- name: Install latest nuxt/ui
run: pnpm install https://pkg.pr.new/@nuxt/ui@${{ env.COMMIT_SHA }} --lockfile-only
- name: Install dependencies
run: pnpm install
- name: Typecheck
run: pnpm run typecheck
- name: Build
run: pnpm run build
nuxt-ui-pro:
needs: build
# 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: permissions:
issues: write issues: write
pull-requests: write
steps: steps:
- uses: actions/stale@v9 - uses: actions/stale@v9
with: with:
days-before-stale: -1 # Issues and PR will never be flagged stale automatically. exempt-issue-labels: triage,v3
stale-issue-label: 'needs reproduction' # Label that flags an issue as stale. stale-issue-message: 'This issue is stale because it has been open for 30 days with no activity.'
only-labels: 'needs reproduction' # Only process these issues stale-issue-label: stale
days-before-issue-close: 7 stale-pr-label: stale
ignore-updates: true days-before-stale: 30
remove-stale-when-updated: false days-before-close: -1
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

View File

@@ -1,102 +1,5 @@
# Changelog # 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) ## [3.0.0-beta.2](https://github.com/nuxt/ui/compare/v3.0.0-beta.1...v3.0.0-beta.2) (2025-02-28)
### Bug Fixes ### Bug Fixes

View File

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

View File

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

View File

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

View File

@@ -33,7 +33,7 @@ const component = ({ name, primitive, pro, prose, content }) => {
import type { AppConfig } from '@nuxt/schema' import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config' import _appConfig from '#build/app.config'
import theme from '#build/${path}/${prose ? 'prose/' : ''}${content ? 'content/' : ''}${kebabName}' 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 ? ' }' : ''} 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 type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config' import _appConfig from '#build/app.config'
import theme from '#build/${path}/${prose ? 'prose/' : ''}${content ? 'content/' : ''}${kebabName}' 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 ? ' }' : ''} const appConfig${camelName} = _appConfig as AppConfig & { ${key}: { ${prose ? 'prose: { ' : ''}${camelName}: Partial<typeof theme> } }${prose ? ' }' : ''}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,9 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import type { ContextMenuItem } from '@nuxt/ui'
const loading = ref(true) const loading = ref(true)
const items: ContextMenuItem[] = [{ const items = [{
label: 'Refresh the Page', label: 'Refresh the Page',
slot: 'refresh' 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"> <script setup lang="ts">
import type { DropdownMenuItem } from '@nuxt/ui'
const showBookmarks = ref(true) const showBookmarks = ref(true)
const showHistory = ref(false) const showHistory = ref(false)
const showDownloads = ref(false) const showDownloads = ref(false)
@@ -38,7 +36,7 @@ const items = computed(() => [{
onUpdateChecked(checked: boolean) { onUpdateChecked(checked: boolean) {
showDownloads.value = checked showDownloads.value = checked
} }
}] satisfies DropdownMenuItem[]) }])
</script> </script>
<template> <template>

View File

@@ -1,7 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import type { DropdownMenuItem } from '@nuxt/ui' const items = [
const items: DropdownMenuItem[][] = [
[ [
{ {
label: 'View', label: 'View',
@@ -19,7 +17,7 @@ const items: DropdownMenuItem[][] = [
[ [
{ {
label: 'Delete', label: 'Delete',
color: 'error', color: 'error' as const,
icon: 'i-lucide-trash' icon: 'i-lucide-trash'
} }
] ]
@@ -29,5 +27,9 @@ const items: DropdownMenuItem[][] = [
<template> <template>
<UDropdownMenu :items="items" :ui="{ content: 'w-48' }"> <UDropdownMenu :items="items" :ui="{ content: 'w-48' }">
<UButton label="Open" color="neutral" variant="outline" icon="i-lucide-menu" /> <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> </UDropdownMenu>
</template> </template>

View File

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

View File

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

View File

@@ -22,7 +22,7 @@ async function onSubmit(event: FormSubmitEvent<Schema>) {
</script> </script>
<template> <template>
<UForm :schema="schema" :state="state" class="space-y-4" @submit="onSubmit"> <UForm :schema="v.safeParser(schema)" :state="state" class="space-y-4" @submit="onSubmit">
<UFormField label="Email" name="email"> <UFormField label="Email" name="email">
<UInput v-model="state.email" /> <UInput v-model="state.email" />
</UFormField> </UFormField>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,4 @@
<script setup lang="ts"> <script setup lang="ts">
import type { SelectItem } from '@nuxt/ui'
const items = ref([ const items = ref([
{ {
label: 'benjamincanac', label: 'benjamincanac',
@@ -25,21 +23,13 @@ const items = ref([
src: 'https://github.com/noook.png', src: 'https://github.com/noook.png',
alt: 'noook' 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 value = ref(items.value[0]?.value)
const avatar = computed(() => items.value.find(item => item.value === value.value)?.avatar) const avatar = computed(() => items.value.find(item => item.value === value.value)?.avatar)
</script> </script>
<template> <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> </template>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -17,7 +17,7 @@ const { data, status } = await useFetch<User[]>('https://jsonplaceholder.typicod
transform: (data) => { transform: (data) => {
return data?.map(user => ({ return data?.map(user => ({
...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 lazy: true

View File

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

View File

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

View File

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

View File

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

View File

@@ -95,7 +95,7 @@ function getDropdownActions(user: User): DropdownMenuItem[][] {
<UTable :data="data" :columns="columns" class="flex-1"> <UTable :data="data" :columns="columns" class="flex-1">
<template #name-cell="{ row }"> <template #name-cell="{ row }">
<div class="flex items-center gap-3"> <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> <div>
<p class="font-medium text-(--ui-text-highlighted)"> <p class="font-medium text-(--ui-text-highlighted)">
{{ row.original.name }} {{ row.original.name }}
@@ -108,7 +108,7 @@ function getDropdownActions(user: User): DropdownMenuItem[][] {
</template> </template>
<template #action-cell="{ row }"> <template #action-cell="{ row }">
<UDropdownMenu :items="getDropdownActions(row.original)"> <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> </UDropdownMenu>
</template> </template>
</UTable> </UTable>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -84,10 +84,10 @@ export function useLinks() {
label: 'Community', label: 'Community',
icon: 'i-lucide-users', icon: 'i-lucide-users',
children: [{ children: [{
icon: 'i-lucide-presentation', label: 'Roadmap',
label: 'Showcase', description: 'Track our development progress in real-time.',
description: 'Check out some amazing projects built with Nuxt UI.', icon: 'i-lucide-map',
to: '/showcase' to: '/roadmap'
}, { }, {
label: 'Devtools Integration', label: 'Devtools Integration',
description: 'Integrate Nuxt UI with Nuxt Devtools with Compodium.', description: 'Integrate Nuxt UI with Nuxt Devtools with Compodium.',
@@ -112,5 +112,5 @@ export function useLinks() {
icon: 'i-lucide-rocket', icon: 'i-lucide-rocket',
to: 'https://github.com/nuxt/ui/releases', to: 'https://github.com/nuxt/ui/releases',
target: '_blank' 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"> <script setup lang="ts">
import colors from 'tailwindcss/colors' import colors from 'tailwindcss/colors'
// import { debounce } from 'perfect-debounce'
import type { NuxtError } from '#app' import type { NuxtError } from '#app'
const props = defineProps<{ const props = defineProps<{
@@ -14,8 +15,17 @@ const { data: files } = useLazyAsyncData('search', () => queryCollectionSearchSe
server: false server: false
}) })
const searchTerm = ref('')
// watch(searchTerm, debounce((query: string) => {
// if (!query) {
// return
// }
// useTrackEvent('Search', { props: { query: `${query} - ${searchTerm.value?.commandPaletteRef.results.length} results` } })
// }, 500))
const links = useLinks() const links = useLinks()
const searchLinks = useSearchLinks()
const color = computed(() => colorMode.value === 'dark' ? (colors as any)[appConfig.ui.colors.neutral][900] : 'white') const color = computed(() => colorMode.value === 'dark' ? (colors as any)[appConfig.ui.colors.neutral][900] : 'white')
const radius = computed(() => `:root { --ui-radius: ${appConfig.theme.radius}rem; }`) const radius = computed(() => `:root { --ui-radius: ${appConfig.theme.radius}rem; }`)
const blackAsPrimary = computed(() => appConfig.theme.blackAsPrimary ? `:root { --ui-primary: black; } .dark { --ui-primary: white; }` : ':root {}') const blackAsPrimary = computed(() => appConfig.theme.blackAsPrimary ? `:root { --ui-primary: black; } .dark { --ui-primary: white; }` : ':root {}')
@@ -38,7 +48,7 @@ useHead({
}) })
useSeoMeta({ useSeoMeta({
titleTemplate: '%s - Nuxt UI', titleTemplate: '%s - Nuxt UI v3',
title: String(props.error.statusCode) title: String(props.error.statusCode)
}) })
@@ -57,17 +67,17 @@ provide('navigation', mappedNavigation)
<UApp> <UApp>
<NuxtLoadingIndicator color="#FFF" /> <NuxtLoadingIndicator color="#FFF" />
<Banner /> <!-- <Banner /> -->
<Header :links="links" /> <Header :links="links" />
<UError :error="error" /> <UError :error="error" />
<Footer /> <!-- <Footer /> -->
<ClientOnly> <ClientOnly>
<LazyUContentSearch <LazyUContentSearch
:links="searchLinks" v-model:search-term="searchTerm"
:files="files" :files="files"
:groups="[{ :groups="[{
id: 'framework', id: 'framework',

View File

@@ -97,7 +97,7 @@ design_system:
``` ```
```css [main.css] ```css [main.css]
@import "tailwindcss"; @import "tailwindcss" theme(static);
@import "@nuxt/ui"; @import "@nuxt/ui";
:root { :root {
@@ -176,11 +176,7 @@ community:
links: links:
- label: Star on GitHub - label: Star on GitHub
color: neutral color: neutral
variant: outline
to: https://github.com/nuxt/ui to: https://github.com/nuxt/ui
target: _blank target: _blank
icon: i-lucide-star 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"> <script setup lang="ts">
import { kebabCase } from 'scule' import { kebabCase } from 'scule'
import type { ContentNavigationItem } from '@nuxt/content' import type { ContentNavigationItem } from '@nuxt/content'
import type { PageLink } from '@nuxt/ui-pro' import { findPageBreadcrumb, mapContentNavigation } from '#ui-pro/utils/content'
import { findPageBreadcrumb, mapContentNavigation } from '@nuxt/ui-pro/utils/content'
const route = useRoute() const route = useRoute()
const { framework, module } = useSharedData() 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 ' : '' const type = page.value?.path.includes('components') ? 'Vue Component ' : page.value?.path.includes('composables') ? 'Vue Composable ' : ''
useSeoMeta({ 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, 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, description: page.value.description,
ogDescription: page.value.description ogDescription: page.value.description
}) })
@@ -83,8 +82,6 @@ if (route.path.startsWith('/components')) {
}) })
} else { } else {
defineOgImageComponent('Docs', { defineOgImageComponent('Docs', {
title: page.value.title,
description: page.value.description,
headline: breadcrumb.value?.[breadcrumb.value.length - 1]?.label || 'Nuxt UI', headline: breadcrumb.value?.[breadcrumb.value.length - 1]?.label || 'Nuxt UI',
framework: page.value?.framework, framework: page.value?.framework,
module: page.value.module module: page.value.module
@@ -101,25 +98,32 @@ const communityLinks = computed(() => [{
label: 'Star on GitHub', label: 'Star on GitHub',
to: `https://github.com/nuxt/${page.value?.module === 'ui-pro' ? 'ui-pro' : 'ui'}`, to: `https://github.com/nuxt/${page.value?.module === 'ui-pro' ? 'ui-pro' : 'ui'}`,
target: '_blank' 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', label: 'Contribution',
to: '/getting-started/contribution' to: '/getting-started/contribution'
}, { }, {
label: 'Roadmap', label: 'Roadmap',
icon: 'i-lucide-map', icon: 'i-lucide-map',
to: '/roadmap' 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> </script>
<template> <template>
@@ -147,7 +151,7 @@ const communityLinks = computed(() => [{
v-bind="link" v-bind="link"
> >
<template v-if="link.avatar" #leading> <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> </template>
</UButton> </UButton>
</template> </template>
@@ -168,9 +172,14 @@ const communityLinks = computed(() => [{
<UPageLinks title="Community" :links="communityLinks" /> <UPageLinks title="Community" :links="communityLinks" />
<!-- <USeparator type="dashed" />
<UPageLinks title="Resources" :links="resourcesLinks" />
<USeparator type="dashed" /> <USeparator type="dashed" />
<AdsCarbon /> <AdsPro />
<AdsCarbon /> -->
</template> </template>
</UContentToc> </UContentToc>
</template> </template>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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