mirror of
https://github.com/ArthurDanjou/ui.git
synced 2026-01-15 12:39:35 +01:00
Compare commits
196 Commits
fix/form-o
...
fix/3394
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c326180f15 | ||
|
|
6887e33aae | ||
|
|
d75093a160 | ||
|
|
47ed1e0f74 | ||
|
|
d6a3a65b8e | ||
|
|
a81d0e55c7 | ||
|
|
5b172b0fb3 | ||
|
|
fbf7475e0d | ||
|
|
0f90645c84 | ||
|
|
33193d782d | ||
|
|
d1f2b50033 | ||
|
|
bd75d2d184 | ||
|
|
28e869e8aa | ||
|
|
cabad480f9 | ||
|
|
d86956e1d5 | ||
|
|
23e4f0ec4d | ||
|
|
91d06d4d51 | ||
|
|
f1128c2450 | ||
|
|
c00f6e8cdf | ||
|
|
d29e1481f2 | ||
|
|
79aa161c6d | ||
|
|
94ea75f441 | ||
|
|
0c368c8ab8 | ||
|
|
c5796c4f82 | ||
|
|
204953b780 | ||
|
|
2e4c3082a1 | ||
|
|
f2fd778c0a | ||
|
|
d79da9d7b6 | ||
|
|
a4429eee09 | ||
|
|
0905b2b3d5 | ||
|
|
c7fba2e0eb | ||
|
|
4167f04205 | ||
|
|
276268d311 | ||
|
|
717e35f098 | ||
|
|
459a0410ab | ||
|
|
b9adc83e78 | ||
|
|
d7a4d029b7 | ||
|
|
3c8d6cd01d | ||
|
|
67da90a2f6 | ||
|
|
894e8a61b6 | ||
|
|
1b6ab271ea | ||
|
|
0dc4678c68 | ||
|
|
e86dc79e51 | ||
|
|
35997377a6 | ||
|
|
12303a87be | ||
|
|
f84ccddcd6 | ||
|
|
869c0708bd | ||
|
|
c63d2f380a | ||
|
|
92632e969e | ||
|
|
f6d7994a55 | ||
|
|
f738f68f76 | ||
|
|
17d6803329 | ||
|
|
732a67aa88 | ||
|
|
bdf129fc38 | ||
|
|
d140acc608 | ||
|
|
cc20a26f07 | ||
|
|
983c6382d1 | ||
|
|
37eabc89bd | ||
|
|
a57844e416 | ||
|
|
2be60cddfe | ||
|
|
09b4699aea | ||
|
|
46c2987ebf | ||
|
|
f244d15b96 | ||
|
|
aaa60c0798 | ||
|
|
5467d71cc2 | ||
|
|
941a54e5e3 | ||
|
|
655f98ffed | ||
|
|
999a0f8467 | ||
|
|
2739939c46 | ||
|
|
2a241c87c3 | ||
|
|
e6e510b848 | ||
|
|
a655da1394 | ||
|
|
3a71256d59 | ||
|
|
404359a6ca | ||
|
|
1e4e9c4708 | ||
|
|
6f07f6bd6e | ||
|
|
4c1093bde4 | ||
|
|
7d51a9e479 | ||
|
|
04bdbcfc6e | ||
|
|
58aa296425 | ||
|
|
d3df3bb929 | ||
|
|
4863775e17 | ||
|
|
7551a85ad2 | ||
|
|
c2bcb8e264 | ||
|
|
88124b85c5 | ||
|
|
9f539c9545 | ||
|
|
41d4ffe5b5 | ||
|
|
d8a0bbe710 | ||
|
|
06e5689da8 | ||
|
|
67d19f582a | ||
|
|
650bc40253 | ||
|
|
2f0f317a12 | ||
|
|
d2a26939ad | ||
|
|
b7a8146898 | ||
|
|
e81464a43e | ||
|
|
4b3d2a7b00 | ||
|
|
6f6fa6ae4a | ||
|
|
c7bb6d4c4b | ||
|
|
c23f85fe33 | ||
|
|
591d59fe89 | ||
|
|
caa3bf9c7e | ||
|
|
2731011bb7 | ||
|
|
aebf0b3dca | ||
|
|
6651987dc6 | ||
|
|
61aabd72e4 | ||
|
|
8dfdd63ce3 | ||
|
|
e6369a6746 | ||
|
|
a4f3f6d531 | ||
|
|
c5bdec0f64 | ||
|
|
87a7828931 | ||
|
|
501468960b | ||
|
|
13cd6be679 | ||
|
|
e421ea57ec | ||
|
|
799d7ae422 | ||
|
|
be9b1f659a | ||
|
|
39e29fccf1 | ||
|
|
195773ec7d | ||
|
|
d7710795df | ||
|
|
53f8b34bef | ||
|
|
df83ab355e | ||
|
|
e5df026993 | ||
|
|
6131871a0d | ||
|
|
9543bce787 | ||
|
|
5e345a8a73 | ||
|
|
8acf3c51db | ||
|
|
f3b8b17dc5 | ||
|
|
bc0a296f9d | ||
|
|
ac4c1946ec | ||
|
|
82b5f322eb | ||
|
|
fb6cf708a6 | ||
|
|
8ab1f75e47 | ||
|
|
e9b19349ba | ||
|
|
b2b206e3f4 | ||
|
|
b779064129 | ||
|
|
94155258e2 | ||
|
|
c63a6dd133 | ||
|
|
79833035de | ||
|
|
79e7c7b729 | ||
|
|
bc06185282 | ||
|
|
6e27304d8c | ||
|
|
b4f8ac7ff7 | ||
|
|
a2fa1accaa | ||
|
|
f486423381 | ||
|
|
975331a7b1 | ||
|
|
75e4792f7f | ||
|
|
4a969a8b9e | ||
|
|
664a8c7524 | ||
|
|
fbcc3139a3 | ||
|
|
1a46394668 | ||
|
|
9ca213bd33 | ||
|
|
3484832822 | ||
|
|
80dfa88ea4 | ||
|
|
d710141a1b | ||
|
|
d3e2a3f33a | ||
|
|
9c3d53a02d | ||
|
|
22edfd708a | ||
|
|
e25aa78050 | ||
|
|
122e8ac8f4 | ||
|
|
5fc6312ab1 | ||
|
|
83f0a24704 | ||
|
|
13c299533f | ||
|
|
db11db6ff1 | ||
|
|
50863635d6 | ||
|
|
29fa46276d | ||
|
|
7a35baebc7 | ||
|
|
b6f6cee1a9 | ||
|
|
d49e0dadee | ||
|
|
2b315fd855 | ||
|
|
f42949820b | ||
|
|
d2ba99797c | ||
|
|
fd23038b1a | ||
|
|
fe3ec0c183 | ||
|
|
1a0d7a3103 | ||
|
|
c31bffad1b | ||
|
|
dd8f7a77a5 | ||
|
|
eb46e31ffb | ||
|
|
8ea99f0c4f | ||
|
|
f6b376110c | ||
|
|
01d8dc72ad | ||
|
|
445aac2d57 | ||
|
|
391828a2c2 | ||
|
|
02b6b38a56 | ||
|
|
47cdc2e1d8 | ||
|
|
a7d3097f8d | ||
|
|
7ac7aa9ba7 | ||
|
|
f9737c8f40 | ||
|
|
4e39cc59f8 | ||
|
|
28accc4aa0 | ||
|
|
5d10f242bd | ||
|
|
2d5c881639 | ||
|
|
ad63753b5e | ||
|
|
e5a1e26f9d | ||
|
|
113e2e7166 | ||
|
|
9d4880be35 | ||
|
|
8dd9d08209 | ||
|
|
f309a46b8d |
4
.github/ISSUE_TEMPLATE/bug-report-v3.yml
vendored
4
.github/ISSUE_TEMPLATE/bug-report-v3.yml
vendored
@@ -5,7 +5,7 @@ body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Before reporting a bug, please make sure that you have read through our [v3 documentation](https://ui.nuxt.com/) and existing [issues](https://github.com/nuxt/ui/issues?q=is%3Aissue%20is%3Aopen%20sort%3Aupdated-desc%20label%3Av3).
|
||||
Before reporting a bug, please make sure that you have read through our [documentation](https://ui.nuxt.com/) and existing [issues](https://github.com/nuxt/ui/issues?q=is%3Aissue%20is%3Aopen%20sort%3Aupdated-desc%20label%3Av3).
|
||||
- type: textarea
|
||||
id: env
|
||||
attributes:
|
||||
@@ -44,7 +44,7 @@ body:
|
||||
id: reproduction
|
||||
attributes:
|
||||
label: Reproduction
|
||||
description: Please provide a reproduction link using the Nuxt template https://codesandbox.io/p/devbox/nuxt-ui3-n3sxks or the Vue template https://codesandbox.io/p/devbox/nuxt-ui3-vue-4h5gqn. A minimal [reproduction is required](https://antfu.me/posts/why-reproductions-are-required) unless you are absolutely sure that the issue is obvious and the provided information is enough to understand the problem. If a report is vague (e.g. just a generic error message) and has no reproduction, it will receive a "needs reproduction" label. If no reproduction is provided we might close it.
|
||||
description: Please provide a reproduction link using the Nuxt template https://codesandbox.io/p/devbox/nuxt-ui3-n3sxks or the Vue template https://codesandbox.io/p/devbox/nuxt-ui3-vue-4h5gqn. A minimal [reproduction is required](https://antfu.me/posts/why-reproductions-are-required) unless you are absolutely sure that the issue is obvious and the provided information is enough to understand the problem. If a report is vague (e.g. just a generic error message) and has no reproduction, it will receive a "needs reproduction" label. If no reproduction is provided, it will be closed automatically after a while.
|
||||
placeholder: https://github.com/my/reproduction
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@@ -5,7 +5,7 @@ body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Before requesting a feature, please make sure that you have read through our [v3 documentation](https://ui.nuxt.com/) and existing [issues](https://github.com/nuxt/ui/issues?q=is%3Aissue%20is%3Aopen%20sort%3Aupdated-desc%20label%3Av3).
|
||||
Before requesting a feature, please make sure that you have read through our [documentation](https://ui.nuxt.com/) and existing [issues](https://github.com/nuxt/ui/issues?q=is%3Aissue%20is%3Aopen%20sort%3Aupdated-desc%20label%3Av3).
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/question-v3.yml
vendored
2
.github/ISSUE_TEMPLATE/question-v3.yml
vendored
@@ -5,7 +5,7 @@ body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Before asking a question, please make sure that you have read through our [v3 documentation](https://ui.nuxt.com/) and existing [issues](https://github.com/nuxt/ui/issues?q=is%3Aissue%20is%3Aopen%20sort%3Aupdated-desc%20label%3Av3).
|
||||
Before asking a question, please make sure that you have read through our [documentation](https://ui.nuxt.com/) and existing [issues](https://github.com/nuxt/ui/issues?q=is%3Aissue%20is%3Aopen%20sort%3Aupdated-desc%20label%3Av3).
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
|
||||
30
.github/reproduire/needs-reproduction.md
vendored
Normal file
30
.github/reproduire/needs-reproduction.md
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
Would you be able to provide a [reproduction](https://nuxt.com/docs/community/reporting-bugs/#create-a-minimal-reproduction)? 🙏
|
||||
|
||||
<details>
|
||||
<summary>More info</summary>
|
||||
|
||||
### Why do I need to provide a reproduction?
|
||||
|
||||
Reproductions make it possible for us to triage and fix issues quickly with a relatively small team. It helps us discover the source of the problem, and also can reveal assumptions you or we might be making.
|
||||
|
||||
### What will happen?
|
||||
|
||||
If you've provided a reproduction, we'll remove the label and try to reproduce the issue. If we can, we'll mark it as a bug and prioritise it based on its severity and how many people we think it might affect.
|
||||
|
||||
If `needs reproduction` labeled issues don't receive any substantial activity (e.g., new comments featuring a reproduction link), they will be closed automatically after a while. That's not because we don't care! At any point, feel free to comment with a reproduction and we'll reopen it.
|
||||
|
||||
### How can I create a reproduction?
|
||||
|
||||
We have templates to create a minimal reproduction:
|
||||
|
||||
* **Nuxt**: https://codesandbox.io/p/devbox/nuxt-ui3-n3sxks
|
||||
* **Vue**: https://codesandbox.io/p/devbox/nuxt-ui3-vue-4h5gqn
|
||||
|
||||
Please ensure that the reproduction is as **minimal** as possible. See more details [in our guide](https://nuxt.com/docs/community/reporting-bugs/#create-a-minimal-reproduction).
|
||||
|
||||
You might also find these other articles interesting and/or helpful:
|
||||
|
||||
- [The Importance of Reproductions](https://antfu.me/posts/why-reproductions-are-required)
|
||||
- [How to Generate a Minimal, Complete, and Verifiable Example](https://stackoverflow.com/help/mcve)
|
||||
|
||||
</details>
|
||||
52
.github/workflows/module.yml
vendored
52
.github/workflows/module.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest] # macos-latest
|
||||
os: ${{ github.event_name == 'pull_request' && fromJSON('["ubuntu-latest"]') || fromJSON('["ubuntu-latest", "windows-latest"]') }} # macos-latest
|
||||
node: [22]
|
||||
|
||||
env:
|
||||
@@ -65,9 +65,57 @@ jobs:
|
||||
run: pnpm run dev:vue:build
|
||||
|
||||
- name: Publish
|
||||
if: matrix.os != 'windows-latest'
|
||||
# Only publish preview package on ubuntu during PRs
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: pnpx pkg-pr-new publish --compact --no-template --pnpm
|
||||
|
||||
playground:
|
||||
needs: build
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./playground
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest] # macos-latest, windows-latest
|
||||
node: [22]
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- 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 --ignore-workspace
|
||||
|
||||
- name: Prepare
|
||||
run: pnpm nuxi prepare
|
||||
|
||||
- name: Typecheck
|
||||
run: pnpm run typecheck
|
||||
|
||||
starter-nuxt:
|
||||
needs: build
|
||||
|
||||
|
||||
54
.github/workflows/release.yml
vendored
Normal file
54
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
name: release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v3*'
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest] # macos-latest, windows-latest
|
||||
node: [22]
|
||||
|
||||
env:
|
||||
NUXT_GITHUB_TOKEN: ${{ secrets.NUXT_GITHUB_TOKEN }}
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
|
||||
- name: Install node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
cache: pnpm
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Prepare
|
||||
run: pnpm run dev:prepare
|
||||
|
||||
- name: Lint
|
||||
run: pnpm run lint
|
||||
|
||||
- name: Typecheck
|
||||
run: pnpm run typecheck
|
||||
|
||||
- name: Test
|
||||
run: pnpm run test run
|
||||
|
||||
- name: Test (vue)
|
||||
run: pnpm run test:vue run
|
||||
|
||||
- name: Publish
|
||||
run: ./scripts/release.sh
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NODE_AUTH_TOKEN }}
|
||||
17
.github/workflows/reproduire.yml
vendored
Normal file
17
.github/workflows/reproduire.yml
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
name: reproduire
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [labeled]
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
reproduire:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: Hebilicious/reproduire@4b686ae9cbb72dad60f001d278b6e3b2ce40a9ac # v0.0.9-mp
|
||||
with:
|
||||
label: needs reproduction
|
||||
@@ -3,6 +3,9 @@
|
||||
"commitMessage": "chore(release): v${version}",
|
||||
"tagName": "v${version}"
|
||||
},
|
||||
"npm": {
|
||||
"publish": false
|
||||
},
|
||||
"github": {
|
||||
"release": true,
|
||||
"releaseName": "v${version}",
|
||||
|
||||
132
CHANGELOG.md
132
CHANGELOG.md
@@ -1,5 +1,137 @@
|
||||
# Changelog
|
||||
|
||||
## [3.1.2](https://github.com/nuxt/ui/compare/v3.1.1...v3.1.2) (2025-05-15)
|
||||
|
||||
### Features
|
||||
|
||||
* **Badge:** add `square` prop ([#4008](https://github.com/nuxt/ui/issues/4008)) ([894e8a6](https://github.com/nuxt/ui/commit/894e8a61b6fea3618fc863bd77678385e9d021c2))
|
||||
* **CheckboxGroup:** add `table` variant ([#3997](https://github.com/nuxt/ui/issues/3997)) ([1b6ab27](https://github.com/nuxt/ui/commit/1b6ab271ea3875a7c77ffe9367c7c341083dd53c))
|
||||
* **components:** add `ui` field in items ([#4060](https://github.com/nuxt/ui/issues/4060)) ([b9adc83](https://github.com/nuxt/ui/commit/b9adc83e787db02507e6e7bb1aabc684eccc197b))
|
||||
* **InputNumber:** add `increment-disabled` / `decrement-disabled` props ([#4141](https://github.com/nuxt/ui/issues/4141)) ([c7fba2e](https://github.com/nuxt/ui/commit/c7fba2e0ebfb7153f3bfb727165d653bbd3dbe54))
|
||||
* **locale:** add Slovenian language ([#4140](https://github.com/nuxt/ui/issues/4140)) ([e86dc79](https://github.com/nuxt/ui/commit/e86dc79e51b2773a77ada5f12d4f0964fbc83354))
|
||||
* **NavigationMenu:** add `collapsible` field in items ([2be60cd](https://github.com/nuxt/ui/commit/2be60cddfe10fd1e2466900fd53e21ee0c877227)), closes [#3353](https://github.com/nuxt/ui/issues/3353) [#3911](https://github.com/nuxt/ui/issues/3911)
|
||||
* **NavigationMenu:** handle `tooltip` in items ([46c2987](https://github.com/nuxt/ui/commit/46c2987ebfd30b2b071a96a745b7270e852e96de)), closes [#4050](https://github.com/nuxt/ui/issues/4050)
|
||||
* **Slider:** handle `tooltip` around thumbs ([d140acc](https://github.com/nuxt/ui/commit/d140acc608c6ae11c0a0531fe443588776ea7807)), closes [#1469](https://github.com/nuxt/ui/issues/1469)
|
||||
* **Toast:** add `progress` prop to hide progress bar ([#4125](https://github.com/nuxt/ui/issues/4125)) ([92632e9](https://github.com/nuxt/ui/commit/92632e969eaa11521a166e50e346753929b7f523))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **Badge/Button:** handle zero value in label correctly ([#4108](https://github.com/nuxt/ui/issues/4108)) ([f244d15](https://github.com/nuxt/ui/commit/f244d15b96d97cd8ba34ba9c18f23965e17e3cef))
|
||||
* **ButtonGroup:** add `z-index` on focused element ([204953b](https://github.com/nuxt/ui/commit/204953b780bde08dbfde230fc8887674449227b7))
|
||||
* **Calendar:** wrong color for today date with `neutral` color ([7d51a9e](https://github.com/nuxt/ui/commit/7d51a9e479cb6105ea37759c5cd67ff9f7702c49)), closes [#4084](https://github.com/nuxt/ui/issues/4084) [#3629](https://github.com/nuxt/ui/issues/3629)
|
||||
* **Checkbox/RadioGroup:** render correct element without `variant` ([f2fd778](https://github.com/nuxt/ui/commit/f2fd778c0a604f2d65aec9f3fe2d54b6d4e8c3a2)), closes [#3998](https://github.com/nuxt/ui/issues/3998)
|
||||
* **CheckboxGroup:** relative `UCheckbox` import ([7551a85](https://github.com/nuxt/ui/commit/7551a85ad2d92b59e2909396affb862403d5b27a)), closes [#4090](https://github.com/nuxt/ui/issues/4090)
|
||||
* **ColorPicker:** make thumb touch draggable ([#4101](https://github.com/nuxt/ui/issues/4101)) ([cc20a26](https://github.com/nuxt/ui/commit/cc20a26f07268d19119ab4c7c254033143bb63f4))
|
||||
* **components:** `class` should have priority over `ui` prop ([e6e510b](https://github.com/nuxt/ui/commit/e6e510b848d995a286a51d50a120d67483e11232))
|
||||
* **FormField:** block form field injection after use ([#4150](https://github.com/nuxt/ui/issues/4150)) ([d79da9d](https://github.com/nuxt/ui/commit/d79da9d7b60c9972af64acd8e6eef4ae7d6bc3eb))
|
||||
* **FormField:** use `div` for `error` and `help` slots ([459a041](https://github.com/nuxt/ui/commit/459a0410ab729fde60865e84632b36903465f57e))
|
||||
* **inertia:** link always render as anchor tag ([#3989](https://github.com/nuxt/ui/issues/3989)) ([e81464a](https://github.com/nuxt/ui/commit/e81464a43ede4e63ce3dc92429bbfef48614f731))
|
||||
* **inertia:** make `useAppConfig` reactive ([12303a8](https://github.com/nuxt/ui/commit/12303a87be62dae84ef774e3a9795deb0ac90cc7))
|
||||
* **Input/Textarea:** handle generic types ([3c8d6cd](https://github.com/nuxt/ui/commit/3c8d6cd01dfafed5844c376f52adbdda0c814420)), closes [nuxt/ui-pro#887](https://github.com/nuxt/ui-pro/issues/887)
|
||||
* **InputNumber:** handle inside button group ([2e4c308](https://github.com/nuxt/ui/commit/2e4c3082a1e66fa597086dc3431fec37fa29ef62)), closes [#4155](https://github.com/nuxt/ui/issues/4155)
|
||||
* **Link:** consistent behavior between nuxt, vue and inertia ([#4134](https://github.com/nuxt/ui/issues/4134)) ([67da90a](https://github.com/nuxt/ui/commit/67da90a2f638124f640c4271d3376c5ff3fab6a1))
|
||||
* **module:** configure `@nuxt/fonts` with default weights ([276268d](https://github.com/nuxt/ui/commit/276268d311f57715cec47bc600a0ccc3d3885682))
|
||||
* **NavigationMenu:** arrow position conflict ([#4137](https://github.com/nuxt/ui/issues/4137)) ([0dc4678](https://github.com/nuxt/ui/commit/0dc4678c68e4b500be49c38336dc75b73843e38d))
|
||||
* **Select:** support more primitive types in `value` field ([#4105](https://github.com/nuxt/ui/issues/4105)) ([09b4699](https://github.com/nuxt/ui/commit/09b4699aeadaa195ea081509f8e237bb2c346238))
|
||||
* **Slider:** handle generic types ([d7a4d02](https://github.com/nuxt/ui/commit/d7a4d029b77d2dfa0b8efcd2755d482fa5e31fd3))
|
||||
* **Stepper:** use `div` tag for `title` & `description` ([a57844e](https://github.com/nuxt/ui/commit/a57844e41676c13ed1af861424961b88cee7b4da)), closes [#4096](https://github.com/nuxt/ui/issues/4096)
|
||||
* **Tabs:** prevent trigger truncate without parent width ([06e5689](https://github.com/nuxt/ui/commit/06e5689da80b36205d0548d5d6b58510938e4a6e)), closes [#4056](https://github.com/nuxt/ui/issues/4056)
|
||||
* **Tabs:** set `focus:outline-none` with `link` variant ([999a0f8](https://github.com/nuxt/ui/commit/999a0f84671fad20fa3dc50c6774af2e0200b32e))
|
||||
* **templates:** dont write unused variants in theme files ([d3df3bb](https://github.com/nuxt/ui/commit/d3df3bb929fe6732f27b182d1664213884a662ec))
|
||||
* **Toaster:** allow `base` slot override ([c63d2f3](https://github.com/nuxt/ui/commit/c63d2f380aac16f1d1e812516df3dca7fa7c8034))
|
||||
* **vue:** make `useAppConfig` reactive ([869c070](https://github.com/nuxt/ui/commit/869c0708bd351c7be44e5e430c348b19dd316db9)), closes [#3952](https://github.com/nuxt/ui/issues/3952)
|
||||
|
||||
## [3.1.1](https://github.com/nuxt/ui/compare/v3.1.0...v3.1.1) (2025-05-02)
|
||||
|
||||
### Features
|
||||
|
||||
* **useOverlay:** add `closeAll` method ([#3984](https://github.com/nuxt/ui/issues/3984)) ([ac4c194](https://github.com/nuxt/ui/commit/ac4c1946ec399aec59b4bce9d538e3ff67868abf))
|
||||
* **useOverlay:** add `isOpen` method to check overlay state ([#4041](https://github.com/nuxt/ui/issues/4041)) ([a4f3f6d](https://github.com/nuxt/ui/commit/a4f3f6d531f9c0281f99085a6688d296f8f13f2f))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **Calendar:** add `place-items-center` to grid row ([#4034](https://github.com/nuxt/ui/issues/4034)) ([8dfdd63](https://github.com/nuxt/ui/commit/8dfdd63ce3b3a0e904f7c013c774cf9aaf46b240))
|
||||
* **defineShortcuts:** bring back `meta` to `ctrl` convert on non macos platforms ([f3b8b17](https://github.com/nuxt/ui/commit/f3b8b17dc5f43936ef7ffb11c1ed7f9a5f94d0bb)), closes [#3869](https://github.com/nuxt/ui/issues/3869) [#3318](https://github.com/nuxt/ui/issues/3318)
|
||||
* **module:** support `nuxt-nightly` ([#3996](https://github.com/nuxt/ui/issues/3996)) ([bc0a296](https://github.com/nuxt/ui/commit/bc0a296f9d68ca72cd991b11cd3489b63c7b13db))
|
||||
* **NavigationMenu:** remove `sm:w-auto` from content slot ([aebf0b3](https://github.com/nuxt/ui/commit/aebf0b3dca50c51c093cb6abf16c4fd995fc1b39)), closes [#3987](https://github.com/nuxt/ui/issues/3987)
|
||||
* **RadioGroup:** improve items `value` field type ([#3995](https://github.com/nuxt/ui/issues/3995)) ([195773e](https://github.com/nuxt/ui/commit/195773ec7dac12ccc3a0a67867751e8ca634cc04))
|
||||
* **templates:** put back args to watch in dev ([#4033](https://github.com/nuxt/ui/issues/4033)) ([c5bdec0](https://github.com/nuxt/ui/commit/c5bdec0f64963ef602975270a09a1ee795cdacf9))
|
||||
* **theme:** add missing `border-bg` / `divide-bg` utilities ([82b5f32](https://github.com/nuxt/ui/commit/82b5f322ebd8a08e63588122bd4ef567dcb8ba8c))
|
||||
* **theme:** add missing `ring-offset-*` utilities ([#3992](https://github.com/nuxt/ui/issues/3992)) ([e5df026](https://github.com/nuxt/ui/commit/e5df0269935be59df759fe0e1378acb2b0d9014a))
|
||||
* **theme:** define default shades for named tailwindcss colors ([8acf3c5](https://github.com/nuxt/ui/commit/8acf3c51db6c2f9443d04be6ba7d9f062c5cf8ab)), closes [#3977](https://github.com/nuxt/ui/issues/3977)
|
||||
* **theme:** improve app config types for `ui` object ([591d59f](https://github.com/nuxt/ui/commit/591d59fe89f1d9bf016c121bf9160f73fe0a290d)), closes [#3579](https://github.com/nuxt/ui/issues/3579)
|
||||
* **theme:** use `[@theme](https://github.com/theme) inline` to properly reference css variables ([6131871](https://github.com/nuxt/ui/commit/6131871a0d124c5942d60dc5dff20981e8542e51)), closes [#4018](https://github.com/nuxt/ui/issues/4018)
|
||||
* **useOverlay:** improve types and docs ([#4012](https://github.com/nuxt/ui/issues/4012)) ([39e29fc](https://github.com/nuxt/ui/commit/39e29fccf1840c723a13237d65002501b2829b70))
|
||||
|
||||
## [3.1.0](https://github.com/nuxt/ui/compare/v3.0.2...v3.1.0) (2025-04-24)
|
||||
|
||||
### ⚠ BREAKING CHANGES
|
||||
|
||||
* **OverlayProvider:** return an overlay instance from `.open()` (#3829)
|
||||
|
||||
### Features
|
||||
|
||||
* **App:** add global `portal` prop ([#3688](https://github.com/nuxt/ui/issues/3688)) ([29fa462](https://github.com/nuxt/ui/commit/29fa46276d6bf69b5b87880c476c6f778c2820bf))
|
||||
* **Carousel:** add `select` event ([#3678](https://github.com/nuxt/ui/issues/3678)) ([22edfd7](https://github.com/nuxt/ui/commit/22edfd708ae3eeadbd4ff6c830cdfd5632948286))
|
||||
* **CheckboxGroup:** new component ([#3862](https://github.com/nuxt/ui/issues/3862)) ([9c3d53a](https://github.com/nuxt/ui/commit/9c3d53a02d6254f6b5c90e5fed826b8aefcdb042))
|
||||
* **components:** add new `content-top` and `content-bottom` slots ([#3886](https://github.com/nuxt/ui/issues/3886)) ([1a46394](https://github.com/nuxt/ui/commit/1a463946681e152aa18372118d0fef4a7d8055a5))
|
||||
* **Form:** add `attach` prop to opt-out of nested form attachement ([#3939](https://github.com/nuxt/ui/issues/3939)) ([1a0d7a3](https://github.com/nuxt/ui/commit/1a0d7a3103cf7591b019ef3ad685e2f3786ef6f2))
|
||||
* **Form:** export loading state ([#3861](https://github.com/nuxt/ui/issues/3861)) ([fdee252](https://github.com/nuxt/ui/commit/fdee2522bb9d8361ff3e9fdd4aa2350be8e49b05))
|
||||
* **InputMenu/SelectMenu:** handle `resetSearchTermOnSelect` ([cea881a](https://github.com/nuxt/ui/commit/cea881abdc139b39df89b503cf2ab872f4246c8f)), closes [#3782](https://github.com/nuxt/ui/issues/3782)
|
||||
* **InputNumber:** add support for `stepSnapping` & `disableWheelChange` props ([#3731](https://github.com/nuxt/ui/issues/3731)) ([f5e6284](https://github.com/nuxt/ui/commit/f5e62849c9313063396ab0e3a9b7d22d98ef69bc))
|
||||
* **locale:** add Bulgarian language ([#3783](https://github.com/nuxt/ui/issues/3783)) ([a0c9731](https://github.com/nuxt/ui/commit/a0c9731f634020e76aa98a9a68d673591d35e8c9))
|
||||
* **locale:** add Kazakh language ([#3875](https://github.com/nuxt/ui/issues/3875)) ([43153c4](https://github.com/nuxt/ui/commit/43153c4e91034b728059e7a9bed05888e48f8890))
|
||||
* **locale:** add Tajik language ([#3850](https://github.com/nuxt/ui/issues/3850)) ([f42a79b](https://github.com/nuxt/ui/commit/f42a79b5efe8dc65430a83799ebb0ee737773820))
|
||||
* **locale:** add Uyghur language ([#3878](https://github.com/nuxt/ui/issues/3878)) ([b7fc69b](https://github.com/nuxt/ui/commit/b7fc69baa718ff65b3988d0fa9f143306fa8fac4))
|
||||
* **Modal/Popover/Slideover:** add `close:prevent` event ([#3958](https://github.com/nuxt/ui/issues/3958)) ([f486423](https://github.com/nuxt/ui/commit/f4864233812eac0ed37e0a2d076a95c285a22c01))
|
||||
* **module:** define default color shades ([#3916](https://github.com/nuxt/ui/issues/3916)) ([7ac7aa9](https://github.com/nuxt/ui/commit/7ac7aa9ba73b6aca1bc29b0de2e95c60b2700135))
|
||||
* **module:** define neutral utilities ([#3629](https://github.com/nuxt/ui/issues/3629)) ([d49e0da](https://github.com/nuxt/ui/commit/d49e0dadeea2a58e05e60b2c461b29ce1d334d2b))
|
||||
* **module:** dynamic `rounded-*` utilities ([#3906](https://github.com/nuxt/ui/issues/3906)) ([f9737c8](https://github.com/nuxt/ui/commit/f9737c8f401bf8bc5307674fad6defe2aeeeb907))
|
||||
* **OverlayProvider:** return an overlay instance from `.open()` ([#3829](https://github.com/nuxt/ui/issues/3829)) ([f3098df](https://github.com/nuxt/ui/commit/f3098df84a3b7f58f7ccc1233bc8b45eab99ee10))
|
||||
* **PinInput:** add `autofocus` / `autofocus-delay` props ([0456670](https://github.com/nuxt/ui/commit/0456670dac1153340220603c8c116e3b71f72ae7)), closes [#3717](https://github.com/nuxt/ui/issues/3717)
|
||||
* **RadioGroup:** add `card` and `table` variants ([#3178](https://github.com/nuxt/ui/issues/3178)) ([4d138ad](https://github.com/nuxt/ui/commit/4d138ad6719a074f5f994006d12745ca05bec9c4))
|
||||
* **Select:** handle `onSelect` field in items ([8640831](https://github.com/nuxt/ui/commit/864083156a79dfb5d0be868658b7f9fc77570178))
|
||||
* **Table:** conditionally apply classes to `tr` and `td` ([#3866](https://github.com/nuxt/ui/issues/3866)) ([80dfa88](https://github.com/nuxt/ui/commit/80dfa88ea442571ee1dc673317cc7baa8cacd8a3))
|
||||
* **Tabs:** add `list-leading` and `list-trailing` slots ([#3837](https://github.com/nuxt/ui/issues/3837)) ([3447a06](https://github.com/nuxt/ui/commit/3447a062b636a469089d6e9bdcfcb3dce9063ee5))
|
||||
* **Textarea:** add `autoresize-delay` prop ([06414d3](https://github.com/nuxt/ui/commit/06414d344b151ad6e1a3225a9f5f1f76d58d319c)), closes [#3730](https://github.com/nuxt/ui/issues/3730)
|
||||
* **Textarea:** add `icon`, `loading`, etc. props to match Input ([cb193f1](https://github.com/nuxt/ui/commit/cb193f1d25b5c73ca03dcf10864800350dd1c290))
|
||||
* **Textarea:** add `resize-none` class with `autoresize` prop ([ffafd81](https://github.com/nuxt/ui/commit/ffafd81e1ed25074430668c792e5e1c6afc22bd0))
|
||||
* **unplugin:** routing support for inertia ([#3845](https://github.com/nuxt/ui/issues/3845)) ([d059efc](https://github.com/nuxt/ui/commit/d059efca258da7ae5116e829189a492824ac1d87))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **Accordion:** use `div` instead of `h3` for header tag ([75e4792](https://github.com/nuxt/ui/commit/75e4792f7f00c55229253289c4f806f2b6fc9854)), closes [#3963](https://github.com/nuxt/ui/issues/3963)
|
||||
* **Alert/Toast:** display actions when using slots ([5086363](https://github.com/nuxt/ui/commit/50863635d653c8083772046ddc5b828fba7047d0)), closes [#3950](https://github.com/nuxt/ui/issues/3950)
|
||||
* **Carousel:** move arrows inside container on mobile ([d339dcb](https://github.com/nuxt/ui/commit/d339dcbfb8fe244bd198d247d8448e3ef856dfef)), closes [#3813](https://github.com/nuxt/ui/issues/3813)
|
||||
* **CheckboxGroup:** proxy slots & `ui` prop ([bc06185](https://github.com/nuxt/ui/commit/bc061852822edd2dfb832a46dd6388123ec5771e))
|
||||
* **CommandPalette:** consistent alignement with other components ([d25265c](https://github.com/nuxt/ui/commit/d25265c8b7d34e01af8827d9af5eccb98bf30e9e))
|
||||
* **CommandPalette:** increase input font size to avoid zoom ([d227a10](https://github.com/nuxt/ui/commit/d227a105d8d409ea0753153afaecf639ddb80fed))
|
||||
* **CommandPalette:** prevent hover background on disabled items ([ba534f1](https://github.com/nuxt/ui/commit/ba534f18b94383c97b2654d892ee4b8b024b3fab))
|
||||
* **components:** refactor types after `@nuxt/module-builder` upgrade ([#3855](https://github.com/nuxt/ui/issues/3855)) ([39c861a](https://github.com/nuxt/ui/commit/39c861a64bbd452256ebd1a14a257b94c35855d4))
|
||||
* **components:** respect `transform-origin` in popper content ([#3919](https://github.com/nuxt/ui/issues/3919)) ([01d8dc7](https://github.com/nuxt/ui/commit/01d8dc72adb0b32ad68bb4a98bf24b17f435a89c))
|
||||
* **ContextMenu/DropdownMenu:** handle RTL mode ([#3744](https://github.com/nuxt/ui/issues/3744)) ([1ae5cc0](https://github.com/nuxt/ui/commit/1ae5cc09cb2eca6b6f53eb04db9dcc731b696cae))
|
||||
* **ContextMenuContent/DropdownMenuContent:** remove unwanted `any` ([#3741](https://github.com/nuxt/ui/issues/3741)) ([97274f1](https://github.com/nuxt/ui/commit/97274f15b8bfe457e7e206f81b32e3febf0f875d))
|
||||
* **Form:** input and output type inference ([#3938](https://github.com/nuxt/ui/issues/3938)) ([f429498](https://github.com/nuxt/ui/commit/f42949820be9be9fca41abc653dc12c033e1eeec))
|
||||
* **Form:** loses focus on submit ([#3796](https://github.com/nuxt/ui/issues/3796)) ([8e78eb1](https://github.com/nuxt/ui/commit/8e78eb15c85beef1c814206c4a192d4eb00a7e86))
|
||||
* **InputMenu/Select/SelectMenu:** add `min-w-fit` to `content` slot ([#3922](https://github.com/nuxt/ui/issues/3922)) ([f6b3761](https://github.com/nuxt/ui/commit/f6b376110c8bee2c41ae3137bb972aad402ebff1))
|
||||
* **InputMenu/SelectMenu:** correctly call `onSelect` events ([#3735](https://github.com/nuxt/ui/issues/3735)) ([f25fed5](https://github.com/nuxt/ui/commit/f25fed58e988b304e79cdb536d544d257395cf89))
|
||||
* **InputMenu/SelectMenu:** prevent `disabled` items to be selected ([8435a0f](https://github.com/nuxt/ui/commit/8435a0fe1622eb5b6863b6e4751c9d2d1be36db9)), closes [#3474](https://github.com/nuxt/ui/issues/3474)
|
||||
* **InputMenu/SelectMenu:** remove `valueKey` string case ([9ca213b](https://github.com/nuxt/ui/commit/9ca213bd3340492d7503a34bd142e1f79a697050)), closes [#3949](https://github.com/nuxt/ui/issues/3949) [#3331](https://github.com/nuxt/ui/issues/3331)
|
||||
* **InputMenu/SelectMenu:** support arbitrary `value` ([#3779](https://github.com/nuxt/ui/issues/3779)) ([52a97e2](https://github.com/nuxt/ui/commit/52a97e2df7903f91e3134931eb0d6bd4c528f71f))
|
||||
* **InputMenu:** emit `change` on multiple item removal ([9d2fed1](https://github.com/nuxt/ui/commit/9d2fed125013e3bbfbf9435678729cd05254a5e8)), closes [#3756](https://github.com/nuxt/ui/issues/3756)
|
||||
* **Link:** proxy `download` property ([#3879](https://github.com/nuxt/ui/issues/3879)) ([47cdc2e](https://github.com/nuxt/ui/commit/47cdc2e1d8cd9803ebc954ccae110d62b9a08779))
|
||||
* **NavigationMenu:** add `sm:w-auto` content slot ([abe0859](https://github.com/nuxt/ui/commit/abe0859691e06564f68335bd82dcd121e976408e)), closes [#3788](https://github.com/nuxt/ui/issues/3788)
|
||||
* **Skeleton:** improve accessibility ([#3613](https://github.com/nuxt/ui/issues/3613)) ([3484832](https://github.com/nuxt/ui/commit/3484832822015a224ce6fbeae5132018875557e6))
|
||||
* **Stepper:** ui prop override on `icon` and `content` slots ([1d45980](https://github.com/nuxt/ui/commit/1d459803dc052a16b8966ee89c71646bf6ef1c16)), closes [#3785](https://github.com/nuxt/ui/issues/3785)
|
||||
* **Table:** improve `data` reactivity ([#3967](https://github.com/nuxt/ui/issues/3967)) ([6e27304](https://github.com/nuxt/ui/commit/6e27304d8ca459a04667bac404084264a8cf58fd))
|
||||
* **Table:** pass header `colspan` to `th` ([#3926](https://github.com/nuxt/ui/issues/3926)) ([122e8ac](https://github.com/nuxt/ui/commit/122e8ac8f41ba093cd350c3ce642263263f77296))
|
||||
* **Tree:** simplify reusable template types ([#3836](https://github.com/nuxt/ui/issues/3836)) ([3deed4c](https://github.com/nuxt/ui/commit/3deed4c271cad4adc2a4c47d5dd02e95a14ce11a))
|
||||
* **types:** allow color identifiers with dashes ([#3896](https://github.com/nuxt/ui/issues/3896)) ([e5a1e26](https://github.com/nuxt/ui/commit/e5a1e26f9db763b54caed4ca313f44d1b5fe269d))
|
||||
* **types:** handle `ClassValue` in `ui` prop ([eea1415](https://github.com/nuxt/ui/commit/eea14155aa612649bc969d806ec5df4295945c70)), closes [#3860](https://github.com/nuxt/ui/issues/3860)
|
||||
* **types:** improve dynamic slots ([#3857](https://github.com/nuxt/ui/issues/3857)) ([8dd9d08](https://github.com/nuxt/ui/commit/8dd9d08209e47a7d9a5654db4fb936b4cbcfc021))
|
||||
* **usePortal:** adjust portal target resolution logic ([#3954](https://github.com/nuxt/ui/issues/3954)) ([db11db6](https://github.com/nuxt/ui/commit/db11db6ff1ce4b27a66aaa03f07870ba36426181))
|
||||
* **vite:** vitest skipping nuxt imports transformations ([#3925](https://github.com/nuxt/ui/issues/3925)) ([c31bffa](https://github.com/nuxt/ui/commit/c31bffad1b8afeda584bca8c73bb7f790eb12a9f))
|
||||
|
||||
## [3.0.2](https://github.com/nuxt/ui/compare/v3.0.1...v3.0.2) (2025-03-28)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -16,6 +16,10 @@ Nuxt UI harnesses the combined strengths of [Reka UI](https://reka-ui.com/), [Ta
|
||||
> [!NOTE]
|
||||
> You are on the `v3` development branch, check out the [v2 branch](https://github.com/nuxt/ui/tree/v2) for Nuxt UI v2.
|
||||
|
||||
> [!TIP]
|
||||
> **Looking for more components ?**
|
||||
> Check out [Nuxt UI Pro](https://ui.nuxt.com/pro), a collection of premium Vue components, composables, and utilities built on top of Nuxt UI for faster and more powerful app development.
|
||||
|
||||
## Documentation
|
||||
|
||||
Visit https://ui.nuxt.com to explore the documentation.
|
||||
|
||||
@@ -11,7 +11,7 @@ export default defineBuildConfig({
|
||||
delimiters: ['', ''],
|
||||
values: {
|
||||
// Used in development to import directly from theme
|
||||
'const isUiDev = true': 'const isUiDev = false'
|
||||
'process.argv.includes(\'--uiDev\')': 'false'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -66,7 +66,7 @@ const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.${camelName}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Primitive :as="as" :class="ui.root({ class: [props.class, props.ui?.root] })">
|
||||
<Primitive :as="as" :class="ui.root({ class: [props.ui?.root, props.class] })">
|
||||
<slot />
|
||||
</Primitive>
|
||||
</template>
|
||||
@@ -109,7 +109,7 @@ const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.${camelName}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<${upperName}Root v-bind="rootProps" :class="ui.root({ class: [props.class, props.ui?.root] })" />
|
||||
<${upperName}Root v-bind="rootProps" :class="ui.root({ class: [props.ui?.root, props.class] })" />
|
||||
</template>
|
||||
`
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ useHead({
|
||||
{ key: 'theme-color', name: 'theme-color', content: color }
|
||||
],
|
||||
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)}` }
|
||||
],
|
||||
style: [
|
||||
@@ -40,6 +40,8 @@ useServerSeoMeta({
|
||||
twitterCard: 'summary_large_image'
|
||||
})
|
||||
|
||||
useFaviconFromTheme()
|
||||
|
||||
const { frameworks, modules } = useSharedData()
|
||||
const { mappedNavigation, filteredNavigation } = useContentNavigation(navigation)
|
||||
|
||||
@@ -51,7 +53,7 @@ provide('navigation', mappedNavigation)
|
||||
<NuxtLoadingIndicator color="var(--ui-primary)" :height="2" />
|
||||
|
||||
<template v-if="!route.path.startsWith('/examples')">
|
||||
<Banner />
|
||||
<!-- <Banner /> -->
|
||||
|
||||
<Header :links="links" />
|
||||
</template>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
@import "tailwindcss" theme(static) source("../../../..");
|
||||
@import "@nuxt/ui-pro";
|
||||
|
||||
@source "../../../content";
|
||||
@source "../../../content/**/*";
|
||||
@source "../../../node_modules/.c12";
|
||||
|
||||
@theme static {
|
||||
|
||||
@@ -23,27 +23,27 @@ onMounted(() => {
|
||||
@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;
|
||||
@apply relative border border-default rounded-md hover: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);
|
||||
@apply !max-w-full w-full rounded-sm;
|
||||
}
|
||||
}
|
||||
|
||||
.carbon-text {
|
||||
@apply text-sm text-(--ui-text-muted) transition-colors text-center text-pretty flex pt-2;
|
||||
@apply text-sm 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;
|
||||
@apply block text-xs text-center text-muted pt-2;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.carbon-text {
|
||||
@apply text-(--ui-text);
|
||||
@apply text-default;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,8 +22,8 @@ const links = [{
|
||||
|
||||
<UFooter>
|
||||
<template #left>
|
||||
<NuxtLink to="https://github.com/nuxt/ui" target="_blank" class="text-sm text-(--ui-text-muted)">
|
||||
Published under <span class="text-(--ui-text-highlighted)">MIT License</span>
|
||||
<NuxtLink to="https://github.com/nuxt/ui" target="_blank" class="text-sm text-muted">
|
||||
Published under <span class="text-highlighted">MIT License</span>
|
||||
</NuxtLink>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -19,8 +19,8 @@ watch(framework, () => {
|
||||
:content="false"
|
||||
color="neutral"
|
||||
:ui="{
|
||||
indicator: 'bg-(--ui-bg)',
|
||||
trigger: 'px-1 data-[state=active]:text-(--ui-text-highlighted)'
|
||||
indicator: 'bg-default',
|
||||
trigger: 'px-1 data-[state=active]:text-highlighted w-full'
|
||||
}"
|
||||
size="xs"
|
||||
@update:model-value="(framework = $event as string)"
|
||||
|
||||
@@ -41,7 +41,7 @@ const mobileLinks = computed(() => [
|
||||
<template>
|
||||
<UHeader :ui="{ left: 'min-w-0' }" :menu="{ shouldScaleBackground: true }">
|
||||
<template #left>
|
||||
<NuxtLink to="/" class="flex items-end gap-2 font-bold text-xl text-(--ui-text-highlighted) min-w-0 focus-visible:outline-(--ui-primary) shrink-0" aria-label="Nuxt UI">
|
||||
<NuxtLink to="/" class="flex items-end gap-2 font-bold text-xl text-highlighted min-w-0 focus-visible:outline-primary shrink-0" aria-label="Nuxt UI">
|
||||
<Logo v-if="route.path === '/'" class="w-auto h-6 shrink-0" />
|
||||
<LogoPro v-else-if="route.path.startsWith('/pro')" class="w-auto h-6 shrink-0" />
|
||||
<template v-else>
|
||||
@@ -63,7 +63,7 @@ const mobileLinks = computed(() => [
|
||||
trailing-icon="i-lucide-chevron-down"
|
||||
size="xs"
|
||||
class="-mb-[6px] font-semibold rounded-full truncate"
|
||||
:class="[open && 'bg-(--ui-primary)/15 ']"
|
||||
:class="[open && 'bg-primary/15 ']"
|
||||
:ui="{
|
||||
trailingIcon: ['transition-transform duration-200', open ? 'rotate-180' : undefined].filter(Boolean).join(' ')
|
||||
}"
|
||||
@@ -108,7 +108,7 @@ const mobileLinks = computed(() => [
|
||||
<span class="inline-flex items-center gap-0.5">
|
||||
{{ link.title }}
|
||||
|
||||
<sup v-if="link.module === 'ui-pro'" class="text-[8px] font-medium text-(--ui-primary)">PRO</sup>
|
||||
<sup v-if="link.module === 'ui-pro'" class="text-[8px] font-medium text-primary">PRO</sup>
|
||||
</span>
|
||||
</template>
|
||||
</UContentNavigation>
|
||||
|
||||
@@ -19,8 +19,8 @@ watch(module, () => {
|
||||
:content="false"
|
||||
color="neutral"
|
||||
:ui="{
|
||||
indicator: 'bg-(--ui-bg)',
|
||||
trigger: 'px-1 data-[state=active]:text-(--ui-text-highlighted)'
|
||||
indicator: 'bg-default',
|
||||
trigger: 'px-1 data-[state=active]:text-highlighted w-full'
|
||||
}"
|
||||
size="xs"
|
||||
@update:model-value="(module = $event as string)"
|
||||
|
||||
@@ -153,7 +153,8 @@ const options = computed(() => {
|
||||
const items = propItems.length
|
||||
? propItems.map((item: any) => ({
|
||||
value: item,
|
||||
label: String(item)
|
||||
label: String(item),
|
||||
chip: key.toLowerCase().endsWith('color') ? { color: item } : undefined
|
||||
}))
|
||||
: prop?.type === 'boolean' || prop?.type === 'boolean | undefined'
|
||||
? [{ value: true, label: 'true' }, { value: false, label: 'false' }]
|
||||
@@ -329,15 +330,15 @@ const { data: ast } = await useAsyncData(`component-code-${name}-${hash({ props:
|
||||
<template>
|
||||
<div class="my-5">
|
||||
<div class="relative">
|
||||
<div v-if="options.length" class="flex flex-wrap items-center gap-2.5 border border-(--ui-border-muted) border-b-0 relative rounded-t-[calc(var(--ui-radius)*1.5)] px-4 py-2.5 overflow-x-auto">
|
||||
<div v-if="options.length" class="flex flex-wrap items-center gap-2.5 border border-muted border-b-0 relative rounded-t-md px-4 py-2.5 overflow-x-auto">
|
||||
<template v-for="option in options" :key="option.name">
|
||||
<UFormField
|
||||
:label="option.label"
|
||||
size="sm"
|
||||
class="inline-flex ring ring-(--ui-border-accented) rounded-(--ui-radius)"
|
||||
class="inline-flex ring ring-accented rounded-sm"
|
||||
:ui="{
|
||||
wrapper: 'bg-(--ui-bg-elevated)/50 rounded-l-(--ui-radius) flex border-r border-(--ui-border-accented)',
|
||||
label: 'text-(--ui-text-muted) px-2 py-1.5',
|
||||
wrapper: 'bg-elevated/50 rounded-l-sm flex border-r border-accented',
|
||||
label: 'text-muted px-2 py-1.5',
|
||||
container: 'mt-0'
|
||||
}"
|
||||
>
|
||||
@@ -348,7 +349,7 @@ const { data: ast } = await useAsyncData(`component-code-${name}-${hash({ props:
|
||||
value-key="value"
|
||||
color="neutral"
|
||||
variant="soft"
|
||||
class="rounded-(--ui-radius) rounded-l-none min-w-12"
|
||||
class="rounded-sm rounded-l-none min-w-12"
|
||||
:class="[option.name.toLowerCase().endsWith('color') && 'pl-6']"
|
||||
:ui="{ itemLeadingChip: 'size-2' }"
|
||||
@update:model-value="setComponentProp(option.name, $event)"
|
||||
@@ -370,14 +371,14 @@ const { data: ast } = await useAsyncData(`component-code-${name}-${hash({ props:
|
||||
:model-value="getComponentProp(option.name)"
|
||||
color="neutral"
|
||||
variant="soft"
|
||||
:ui="{ base: 'rounded-(--ui-radius) rounded-l-none min-w-12' }"
|
||||
:ui="{ base: 'rounded-sm rounded-l-none min-w-12' }"
|
||||
@update:model-value="setComponentProp(option.name, $event)"
|
||||
/>
|
||||
</UFormField>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div v-if="component" class="flex justify-center border border-b-0 border-(--ui-border-muted) relative p-4 z-[1]" :class="[!options.length && 'rounded-t-[calc(var(--ui-radius)*1.5)]', props.class, { 'overflow-hidden': props.overflowHidden }]">
|
||||
<div v-if="component" class="flex justify-center border border-b-0 border-muted relative p-4 z-[1]" :class="[!options.length && 'rounded-t-md', props.class, { 'overflow-hidden': props.overflowHidden }]">
|
||||
<component :is="component" v-bind="{ ...componentProps, ...componentEvents }">
|
||||
<template v-for="slot in Object.keys(slots || {})" :key="slot" #[slot]>
|
||||
<slot :name="slot" mdc-unwrap="p">
|
||||
|
||||
@@ -150,8 +150,8 @@ const urlSearchParams = computed(() => {
|
||||
<template>
|
||||
<div ref="el" class="my-5">
|
||||
<template v-if="preview">
|
||||
<div class="border border-(--ui-border-muted) relative z-[1]" :class="[{ 'border-b-0 rounded-t-[calc(var(--ui-radius)*1.5)]': props.source, 'rounded-[calc(var(--ui-radius)*1.5)]': !props.source, 'overflow-hidden': props.overflowHidden }]">
|
||||
<div v-if="props.options?.length || !!slots.options" class="flex gap-4 p-4 border-b border-(--ui-border-muted)">
|
||||
<div class="border border-muted relative z-[1]" :class="[{ 'border-b-0 rounded-t-md': props.source, 'rounded-md': !props.source, 'overflow-hidden': props.overflowHidden }]">
|
||||
<div v-if="props.options?.length || !!slots.options" class="flex gap-4 p-4 border-b border-muted">
|
||||
<slot name="options" />
|
||||
|
||||
<UFormField
|
||||
@@ -160,10 +160,10 @@ const urlSearchParams = computed(() => {
|
||||
:label="option.label"
|
||||
:name="option.name"
|
||||
size="sm"
|
||||
class="inline-flex ring ring-(--ui-border-accented) rounded-(--ui-radius)"
|
||||
class="inline-flex ring ring-accented rounded-sm"
|
||||
:ui="{
|
||||
wrapper: 'bg-(--ui-bg-elevated)/50 rounded-l-(--ui-radius) flex border-r border-(--ui-border-accented)',
|
||||
label: 'text-(--ui-text-muted) px-2 py-1.5',
|
||||
wrapper: 'bg-elevated/50 rounded-l-sm flex border-r border-accented',
|
||||
label: 'text-muted px-2 py-1.5',
|
||||
container: 'mt-0'
|
||||
}"
|
||||
>
|
||||
@@ -175,7 +175,7 @@ const urlSearchParams = computed(() => {
|
||||
:value-key="option.name.toLowerCase().endsWith('color') ? 'value' : undefined"
|
||||
color="neutral"
|
||||
variant="soft"
|
||||
class="rounded-(--ui-radius) rounded-l-none min-w-12"
|
||||
class="rounded-sm rounded-l-none min-w-12"
|
||||
:multiple="option.multiple"
|
||||
:class="[option.name.toLowerCase().endsWith('color') && 'pl-6']"
|
||||
:ui="{ itemLeadingChip: 'size-2' }"
|
||||
@@ -196,7 +196,7 @@ const urlSearchParams = computed(() => {
|
||||
:model-value="get(optionsValues, option.name)"
|
||||
color="neutral"
|
||||
variant="soft"
|
||||
:ui="{ base: 'rounded-(--ui-radius) rounded-l-none min-w-12' }"
|
||||
:ui="{ base: 'rounded-sm rounded-l-none min-w-12' }"
|
||||
@update:model-value="set(optionsValues, option.name, $event)"
|
||||
/>
|
||||
</UFormField>
|
||||
|
||||
@@ -112,7 +112,7 @@ const metaProps: ComputedRef<ComponentMeta['props']> = computed(() => {
|
||||
<ProseTd>
|
||||
<HighlightInlineType v-if="prop.type" :type="prop.type" />
|
||||
|
||||
<MDC v-if="prop.description" :value="prop.description" class="text-(--ui-text-toned) mt-1" :cache-key="`${kebabCase(route.path)}-${prop.name}-description`" />
|
||||
<MDC v-if="prop.description" :value="prop.description" class="text-toned mt-1" :cache-key="`${kebabCase(route.path)}-${prop.name}-description`" />
|
||||
|
||||
<ComponentPropsLinks v-if="prop.tags?.length" :prop="prop" />
|
||||
<ComponentPropsSchema v-if="prop.schema" :prop="prop" :ignore="ignore" />
|
||||
|
||||
@@ -38,12 +38,12 @@ const schemaProps = computed(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ProseCollapsible v-if="schemaProps?.length" class="mt-1">
|
||||
<ProseCollapsible v-if="schemaProps?.length" class="mt-1 mb-0">
|
||||
<ProseUl>
|
||||
<ProseLi v-for="schemaProp in schemaProps" :key="schemaProp.name">
|
||||
<HighlightInlineType :type="`${schemaProp.name}${schemaProp.required === false ? '?' : ''}: ${schemaProp.type}`" />
|
||||
|
||||
<MDC v-if="schemaProp.description" :value="schemaProp.description" class="text-(--ui-text-muted) my-1" :cache-key="`${kebabCase(route.path)}-${prop.name}-${schemaProp.name}-description`" />
|
||||
<MDC v-if="schemaProp.description" :value="schemaProp.description" class="text-muted my-1" :cache-key="`${kebabCase(route.path)}-${prop.name}-${schemaProp.name}-description`" />
|
||||
</ProseLi>
|
||||
</ProseUl>
|
||||
</ProseCollapsible>
|
||||
|
||||
@@ -36,7 +36,7 @@ const meta = await fetchComponentMeta(name as any)
|
||||
<ProseTd>
|
||||
<HighlightInlineType v-if="slot.type" :type="slot.type" />
|
||||
|
||||
<MDC v-if="slot.description" :value="slot.description" class="text-(--ui-text-toned) mt-1" :cache-key="`${kebabCase(route.path)}-${slot.name}-description`" />
|
||||
<MDC v-if="slot.description" :value="slot.description" class="text-toned mt-1" :cache-key="`${kebabCase(route.path)}-${slot.name}-description`" />
|
||||
</ProseTd>
|
||||
</ProseTr>
|
||||
</ProseTbody>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="relative overflow-hidden rounded-(--ui-radius) border border-dashed border-(--ui-border-accented) opacity-75 px-4 flex items-center justify-center">
|
||||
<svg class="absolute inset-0 h-full w-full stroke-(--ui-border-inverted)/10" fill="none">
|
||||
<div class="relative overflow-hidden rounded-sm border border-dashed border-accented opacity-75 px-4 flex items-center justify-center">
|
||||
<svg class="absolute inset-0 h-full w-full stroke-inverted/10" fill="none">
|
||||
<defs>
|
||||
<pattern
|
||||
id="pattern-5c1e4f0e-62d5-498b-8ff0-cf77bb448c8e"
|
||||
|
||||
@@ -25,7 +25,9 @@ function getEmojiFlag(locale: string): string {
|
||||
kk: 'kz', // Kazakh -> Kazakhstan
|
||||
km: 'kh', // Khmer -> Cambodia
|
||||
ko: 'kr', // Korean -> South Korea
|
||||
ms: 'my', // Malay -> Malaysia
|
||||
nb: 'no', // Norwegian Bokmål -> Norway
|
||||
sl: 'si', // Slovenian -> Slovenia
|
||||
sv: 'se', // Swedish -> Sweden
|
||||
uk: 'ua', // Ukrainian -> Ukraine
|
||||
ur: 'pk', // Urdu -> Pakistan
|
||||
|
||||
@@ -20,7 +20,7 @@ const items: AccordionItem[] = [
|
||||
<template>
|
||||
<UAccordion :items="items">
|
||||
<template #content="{ item }">
|
||||
<p class="pb-3.5 text-sm text-(--ui-text-muted)">
|
||||
<p class="pb-3.5 text-sm text-muted">
|
||||
This is the {{ item.label }} panel.
|
||||
</p>
|
||||
</template>
|
||||
|
||||
@@ -24,7 +24,7 @@ const items = [
|
||||
<template>
|
||||
<UAccordion :items="items">
|
||||
<template #colors="{ item }">
|
||||
<p class="text-sm pb-3.5 text-(--ui-primary)">
|
||||
<p class="text-sm pb-3.5 text-primary">
|
||||
{{ item.content }}
|
||||
</p>
|
||||
</template>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<ULink
|
||||
to="https://github.com/benjamincanac"
|
||||
target="_blank"
|
||||
class="hover:ring-(--ui-primary) transition"
|
||||
class="hover:ring-primary transition"
|
||||
raw
|
||||
>
|
||||
<UAvatar
|
||||
@@ -15,7 +15,7 @@
|
||||
<ULink
|
||||
to="https://github.com/romhml"
|
||||
target="_blank"
|
||||
class="hover:ring-(--ui-primary) transition"
|
||||
class="hover:ring-primary transition"
|
||||
raw
|
||||
>
|
||||
<UAvatar
|
||||
@@ -27,7 +27,7 @@
|
||||
<ULink
|
||||
to="https://github.com/noook"
|
||||
target="_blank"
|
||||
class="hover:ring-(--ui-primary) transition"
|
||||
class="hover:ring-primary transition"
|
||||
raw
|
||||
>
|
||||
<UAvatar
|
||||
|
||||
@@ -20,7 +20,7 @@ const items: BreadcrumbItem[] = [
|
||||
<template>
|
||||
<UBreadcrumb :items="items">
|
||||
<template #separator>
|
||||
<span class="mx-2 text-(--ui-text-muted)">/</span>
|
||||
<span class="mx-2 text-muted">/</span>
|
||||
</template>
|
||||
</UBreadcrumb>
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
import { CalendarDate } from '@internationalized/date'
|
||||
|
||||
const date = shallowRef(new CalendarDate(2025, 4, 2))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col gap-4">
|
||||
<UCalendar v-model="date" :month-controls="false" :year-controls="false" />
|
||||
|
||||
<div class="flex justify-between gap-4">
|
||||
<UButton color="neutral" variant="outline" @click="date = date.subtract({ months: 1 })">
|
||||
Prev
|
||||
</UButton>
|
||||
|
||||
<UButton color="neutral" variant="outline" @click="date = date.add({ months: 1 })">
|
||||
Next
|
||||
</UButton>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,58 @@
|
||||
<script setup lang="ts">
|
||||
const items = [
|
||||
'https://picsum.photos/640/640?random=1',
|
||||
'https://picsum.photos/640/640?random=2',
|
||||
'https://picsum.photos/640/640?random=3',
|
||||
'https://picsum.photos/640/640?random=4',
|
||||
'https://picsum.photos/640/640?random=5',
|
||||
'https://picsum.photos/640/640?random=6'
|
||||
]
|
||||
|
||||
const carousel = useTemplateRef('carousel')
|
||||
const activeIndex = ref(0)
|
||||
|
||||
function onClickPrev() {
|
||||
activeIndex.value--
|
||||
}
|
||||
function onClickNext() {
|
||||
activeIndex.value++
|
||||
}
|
||||
function onSelect(index: number) {
|
||||
activeIndex.value = index
|
||||
}
|
||||
|
||||
function select(index: number) {
|
||||
activeIndex.value = index
|
||||
|
||||
carousel.value?.emblaApi?.scrollTo(index)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex-1 w-full">
|
||||
<UCarousel
|
||||
ref="carousel"
|
||||
v-slot="{ item }"
|
||||
arrows
|
||||
:items="items"
|
||||
:prev="{ onClick: onClickPrev }"
|
||||
:next="{ onClick: onClickNext }"
|
||||
class="w-full max-w-xs mx-auto"
|
||||
@select="onSelect"
|
||||
>
|
||||
<img :src="item" width="320" height="320" class="rounded-lg">
|
||||
</UCarousel>
|
||||
|
||||
<div class="flex gap-1 justify-between pt-4 max-w-xs mx-auto">
|
||||
<div
|
||||
v-for="(item, index) in items"
|
||||
:key="index"
|
||||
class="size-11 opacity-25 hover:opacity-100 transition-opacity"
|
||||
:class="{ 'opacity-100': activeIndex === index }"
|
||||
@click="select(index)"
|
||||
>
|
||||
<img :src="item" width="44" height="44" class="rounded-lg">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,9 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import { refDebounced } from '@vueuse/core'
|
||||
|
||||
const searchTerm = ref('')
|
||||
const searchTermDebounced = refDebounced(searchTerm, 200)
|
||||
|
||||
const { data: users, status } = await useFetch('https://jsonplaceholder.typicode.com/users', {
|
||||
key: 'command-palette-users',
|
||||
params: { q: searchTermDebounced },
|
||||
transform: (data: { id: number, name: string, email: string }[]) => {
|
||||
return data?.map(user => ({ id: user.id, label: user.name, suffix: user.email, avatar: { src: `https://i.pravatar.cc/120?img=${user.id}` } })) || []
|
||||
|
||||
@@ -35,7 +35,7 @@ const items = computed<ContextMenuItem[]>(() => [{
|
||||
|
||||
<template>
|
||||
<UContextMenu :items="items" :ui="{ content: 'w-48' }">
|
||||
<div class="flex items-center justify-center rounded-md border border-dashed border-(--ui-border-accented) text-sm aspect-video w-72">
|
||||
<div class="flex items-center justify-center rounded-md border border-dashed border-accented text-sm aspect-video w-72">
|
||||
Right click here
|
||||
</div>
|
||||
</UContextMenu>
|
||||
|
||||
@@ -28,7 +28,7 @@ const items: ContextMenuItem[][] = [
|
||||
|
||||
<template>
|
||||
<UContextMenu :items="items" :ui="{ content: 'w-48' }">
|
||||
<div class="flex items-center justify-center rounded-md border border-dashed border-(--ui-border-accented) text-sm aspect-video w-72">
|
||||
<div class="flex items-center justify-center rounded-md border border-dashed border-accented text-sm aspect-video w-72">
|
||||
Right click here
|
||||
</div>
|
||||
</UContextMenu>
|
||||
|
||||
@@ -19,7 +19,7 @@ const items = [
|
||||
|
||||
<template>
|
||||
<UContextMenu :items="items" :ui="{ content: 'w-48' }">
|
||||
<div class="flex items-center justify-center rounded-md border border-dashed border-(--ui-border-accented) text-sm aspect-video w-72">
|
||||
<div class="flex items-center justify-center rounded-md border border-dashed border-accented text-sm aspect-video w-72">
|
||||
Right click here
|
||||
</div>
|
||||
|
||||
@@ -28,7 +28,7 @@ const items = [
|
||||
</template>
|
||||
|
||||
<template #refresh-trailing>
|
||||
<UIcon v-if="loading" name="i-lucide-refresh-cw" class="shrink-0 size-5 text-(--ui-primary) animate-spin" />
|
||||
<UIcon v-if="loading" name="i-lucide-refresh-cw" class="shrink-0 size-5 text-primary animate-spin" />
|
||||
</template>
|
||||
</UContextMenu>
|
||||
</template>
|
||||
|
||||
@@ -7,7 +7,7 @@ const open = ref(false)
|
||||
<UButton label="Open" color="neutral" variant="subtle" trailing-icon="i-lucide-chevron-up" />
|
||||
|
||||
<template #header>
|
||||
<h2 class="text-(--ui-text-highlighted) font-semibold">
|
||||
<h2 class="text-highlighted font-semibold">
|
||||
Drawer non-dismissible
|
||||
</h2>
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ const items = [
|
||||
<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)" />
|
||||
<UIcon name="i-lucide-badge-check" class="shrink-0 size-5 text-primary" />
|
||||
</template>
|
||||
</UDropdownMenu>
|
||||
</template>
|
||||
|
||||
@@ -30,6 +30,9 @@ const schema = z.object({
|
||||
radioGroup: z.string().refine(value => value === 'option-2', {
|
||||
message: 'Select Option 2'
|
||||
}),
|
||||
checkboxGroup: z.any().refine(values => !!values?.find((option: any) => option === 'option-2'), {
|
||||
message: 'Include Option 2'
|
||||
}),
|
||||
slider: z.number().max(20, { message: 'Must be less than 20' }),
|
||||
pin: z.string().regex(/^\d$/).array().length(5)
|
||||
})
|
||||
@@ -101,11 +104,14 @@ async function onSubmit(event: FormSubmitEvent<Schema>) {
|
||||
<UFormField label="Textarea" name="textarea">
|
||||
<UTextarea v-model="state.textarea" class="w-full" />
|
||||
</UFormField>
|
||||
|
||||
<UFormField name="radioGroup">
|
||||
<URadioGroup v-model="state.radioGroup" legend="Radio group" :items="items" />
|
||||
</UFormField>
|
||||
|
||||
<div class="flex gap-4">
|
||||
<UFormField name="radioGroup">
|
||||
<URadioGroup v-model="state.radioGroup" legend="Radio group" :items="items" />
|
||||
</UFormField>
|
||||
<UFormField name="checkboxGroup">
|
||||
<UCheckboxGroup v-model="state.checkboxGroup" legend="Checkbox group" :items="items" />
|
||||
</UFormField>
|
||||
</div>
|
||||
<UFormField name="pin" label="Pin Input" :error-pattern="/(pin)\..*/">
|
||||
<UPinInput v-model="state.pin" />
|
||||
</UFormField>
|
||||
|
||||
@@ -39,7 +39,7 @@ async function onSubmit(event: FormSubmitEvent<Schema>) {
|
||||
<UCheckbox v-model="state.news" name="news" label="Register to our newsletter" @update:model-value="state.email = undefined" />
|
||||
</div>
|
||||
|
||||
<UForm v-if="state.news" :state="state" :schema="nestedSchema">
|
||||
<UForm v-if="state.news" :state="state" :schema="nestedSchema" attach>
|
||||
<UFormField label="Email" name="email">
|
||||
<UInput v-model="state.email" placeholder="john@lennon.com" />
|
||||
</UFormField>
|
||||
|
||||
@@ -51,7 +51,14 @@ async function onSubmit(event: FormSubmitEvent<Schema>) {
|
||||
<UInput v-model="state.customer" placeholder="Wonka Industries" />
|
||||
</UFormField>
|
||||
|
||||
<UForm v-for="item, count in state.items" :key="count" :state="item" :schema="itemSchema" class="flex gap-2">
|
||||
<UForm
|
||||
v-for="item, count in state.items"
|
||||
:key="count"
|
||||
:state="item"
|
||||
:schema="itemSchema"
|
||||
attach
|
||||
class="flex gap-2"
|
||||
>
|
||||
<UFormField :label="!count ? 'Description' : undefined" name="description">
|
||||
<UInput v-model="item.description" />
|
||||
</UFormField>
|
||||
|
||||
@@ -36,7 +36,7 @@ const { data: users, status } = await useFetch('https://jsonplaceholder.typicode
|
||||
<template #item-label="{ item }">
|
||||
{{ item.label }}
|
||||
|
||||
<span class="text-(--ui-text-muted)">
|
||||
<span class="text-muted">
|
||||
{{ item.email }}
|
||||
</span>
|
||||
</template>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { refDebounced } from '@vueuse/core'
|
||||
import type { AvatarProps } from '@nuxt/ui'
|
||||
|
||||
const searchTerm = ref('')
|
||||
const searchTermDebounced = refDebounced(searchTerm, 200)
|
||||
|
||||
const { data: users, status } = await useFetch('https://jsonplaceholder.typicode.com/users', {
|
||||
key: 'typicode-users',
|
||||
params: { q: searchTermDebounced },
|
||||
transform: (data: { id: number, name: string }[]) => {
|
||||
return data?.map(user => ({
|
||||
|
||||
@@ -15,7 +15,7 @@ const domain = ref(domains[0])
|
||||
}"
|
||||
>
|
||||
<template #leading>
|
||||
<p class="text-sm text-(--ui-text-muted)">
|
||||
<p class="text-sm text-muted">
|
||||
https://
|
||||
</p>
|
||||
</template>
|
||||
|
||||
@@ -13,7 +13,7 @@ const maxLength = 15
|
||||
<template #trailing>
|
||||
<div
|
||||
id="character-count"
|
||||
class="text-xs text-(--ui-text-muted) tabular-nums"
|
||||
class="text-xs text-muted tabular-nums"
|
||||
aria-live="polite"
|
||||
role="status"
|
||||
>
|
||||
|
||||
@@ -4,8 +4,8 @@ const value = ref('')
|
||||
|
||||
<template>
|
||||
<UInput v-model="value" placeholder="" :ui="{ base: 'peer' }">
|
||||
<label class="pointer-events-none absolute left-0 -top-2.5 text-(--ui-text-highlighted) text-xs font-medium px-1.5 transition-all peer-focus:-top-2.5 peer-focus:text-(--ui-text-highlighted) peer-focus:text-xs peer-focus:font-medium peer-placeholder-shown:text-sm peer-placeholder-shown:text-(--ui-text-dimmed) peer-placeholder-shown:top-1.5 peer-placeholder-shown:font-normal">
|
||||
<span class="inline-flex bg-(--ui-bg) px-1">Email address</span>
|
||||
<label class="pointer-events-none absolute left-0 -top-2.5 text-highlighted text-xs font-medium px-1.5 transition-all peer-focus:-top-2.5 peer-focus:text-highlighted peer-focus:text-xs peer-focus:font-medium peer-placeholder-shown:text-sm peer-placeholder-shown:text-dimmed peer-placeholder-shown:top-1.5 peer-placeholder-shown:font-normal">
|
||||
<span class="inline-flex bg-default px-1">Email address</span>
|
||||
</label>
|
||||
</UInput>
|
||||
</template>
|
||||
|
||||
@@ -77,7 +77,7 @@ const text = computed(() => {
|
||||
v-for="(req, index) in strength"
|
||||
:key="index"
|
||||
class="flex items-center gap-0.5"
|
||||
:class="req.met ? 'text-(--ui-success)' : 'text-(--ui-text-muted)'"
|
||||
:class="req.met ? 'text-success' : 'text-muted'"
|
||||
>
|
||||
<UIcon :name="req.met ? 'i-lucide-circle-check' : 'i-lucide-circle-x'" class="size-4 shrink-0" />
|
||||
|
||||
|
||||
@@ -13,7 +13,9 @@ const modal = overlay.create(LazyModalExample, {
|
||||
})
|
||||
|
||||
async function open() {
|
||||
const shouldIncrement = await modal.open()
|
||||
const instance = modal.open()
|
||||
|
||||
const shouldIncrement = await instance.result
|
||||
|
||||
if (shouldIncrement) {
|
||||
count.value++
|
||||
|
||||
@@ -65,6 +65,7 @@ const items = [
|
||||
class="w-full justify-center"
|
||||
:ui="{
|
||||
viewport: 'sm:w-(--reka-navigation-menu-viewport-width)',
|
||||
content: 'sm:w-auto',
|
||||
childList: 'sm:w-96',
|
||||
childLinkDescription: 'text-balance line-clamp-2'
|
||||
}"
|
||||
@@ -76,11 +77,11 @@ const items = [
|
||||
</li>
|
||||
|
||||
<li v-for="child in item.children" :key="child.label">
|
||||
<ULink class="text-sm text-left rounded-md p-3 transition-colors hover:bg-(--ui-bg-elevated)/50">
|
||||
<p class="font-medium text-(--ui-text-highlighted)">
|
||||
<ULink class="text-sm text-left rounded-md p-3 transition-colors hover:bg-elevated/50">
|
||||
<p class="font-medium text-highlighted">
|
||||
{{ child.label }}
|
||||
</p>
|
||||
<p class="text-(--ui-text-muted) line-clamp-2">
|
||||
<p class="text-muted line-clamp-2">
|
||||
{{ child.description }}
|
||||
</p>
|
||||
</ULink>
|
||||
|
||||
@@ -8,7 +8,7 @@ const open = ref(false)
|
||||
|
||||
<template #content>
|
||||
<div class="flex items-center gap-4 mb-4">
|
||||
<h2 class="text-(--ui-text-highlighted) font-semibold">
|
||||
<h2 class="text-highlighted font-semibold">
|
||||
Popover non-dismissible
|
||||
</h2>
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ const { data: users, status } = await useFetch('https://jsonplaceholder.typicode
|
||||
<template #item-label="{ item }">
|
||||
{{ item.label }}
|
||||
|
||||
<span class="text-(--ui-text-muted)">
|
||||
<span class="text-muted">
|
||||
{{ item.email }}
|
||||
</span>
|
||||
</template>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { refDebounced } from '@vueuse/core'
|
||||
import type { AvatarProps } from '@nuxt/ui'
|
||||
|
||||
const searchTerm = ref('')
|
||||
const searchTermDebounced = refDebounced(searchTerm, 200)
|
||||
|
||||
const { data: users, status } = await useFetch('https://jsonplaceholder.typicode.com/users', {
|
||||
key: 'typicode-users',
|
||||
params: { q: searchTermDebounced },
|
||||
transform: (data: { id: number, name: string }[]) => {
|
||||
return data?.map(user => ({
|
||||
|
||||
@@ -13,7 +13,9 @@ const slideover = overlay.create(LazySlideoverExample, {
|
||||
})
|
||||
|
||||
async function open() {
|
||||
const shouldIncrement = await slideover.open()
|
||||
const instance = slideover.open()
|
||||
|
||||
const shouldIncrement = await instance.result
|
||||
|
||||
if (shouldIncrement) {
|
||||
count.value++
|
||||
|
||||
@@ -100,7 +100,7 @@ const columnFilters = ref([{
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col flex-1 w-full">
|
||||
<div class="flex px-4 py-3.5 border-b border-(--ui-border-accented)">
|
||||
<div class="flex px-4 py-3.5 border-b border-accented">
|
||||
<UInput
|
||||
:model-value="(table?.tableApi?.getColumn('email')?.getFilterValue() as string)"
|
||||
class="max-w-sm"
|
||||
|
||||
@@ -131,7 +131,7 @@ function getHeader(column: Column<Payment>, label: string) {
|
||||
'variant': 'ghost',
|
||||
label,
|
||||
'icon': isSorted ? (isSorted === 'asc' ? 'i-lucide-arrow-up-narrow-wide' : 'i-lucide-arrow-down-wide-narrow') : 'i-lucide-arrow-up-down',
|
||||
'class': '-mx-2.5 data-[state=open]:bg-(--ui-bg-elevated)',
|
||||
'class': '-mx-2.5 data-[state=open]:bg-elevated',
|
||||
'aria-label': `Sort by ${isSorted === 'asc' ? 'descending' : 'ascending'}`
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ const columnVisibility = ref({
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col flex-1 w-full">
|
||||
<div class="flex justify-end px-4 py-3.5 border-b border-(--ui-border-accented)">
|
||||
<div class="flex justify-end px-4 py-3.5 border-b border-accented">
|
||||
<UDropdownMenu
|
||||
:items="table?.tableApi?.getAllColumns().filter(column => column.getCanHide()).map(column => ({
|
||||
label: upperFirst(column.id),
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
<script setup lang="ts">
|
||||
import type { TableColumn } from '@nuxt/ui'
|
||||
import { useSortable } from '@vueuse/integrations/useSortable.mjs'
|
||||
|
||||
type Payment = {
|
||||
id: string
|
||||
date: string
|
||||
email: string
|
||||
amount: number
|
||||
}
|
||||
|
||||
const data = ref<Payment[]>([{
|
||||
id: '4600',
|
||||
date: '2024-03-11T15:30:00',
|
||||
email: 'james.anderson@example.com',
|
||||
amount: 594
|
||||
}, {
|
||||
id: '4599',
|
||||
date: '2024-03-11T10:10:00',
|
||||
email: 'mia.white@example.com',
|
||||
amount: 276
|
||||
}, {
|
||||
id: '4598',
|
||||
date: '2024-03-11T08:50:00',
|
||||
email: 'william.brown@example.com',
|
||||
amount: 315
|
||||
}, {
|
||||
id: '4597',
|
||||
date: '2024-03-10T19:45:00',
|
||||
email: 'emma.davis@example.com',
|
||||
amount: 529
|
||||
}])
|
||||
|
||||
const columns: TableColumn<Payment>[] = [{
|
||||
accessorKey: 'id',
|
||||
header: '#',
|
||||
cell: ({ row }) => `#${row.getValue('id')}`
|
||||
}, {
|
||||
accessorKey: 'date',
|
||||
header: 'Date',
|
||||
cell: ({ row }) => {
|
||||
return new Date(row.getValue('date')).toLocaleString('en-US', {
|
||||
day: 'numeric',
|
||||
month: 'short',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
hour12: false
|
||||
})
|
||||
}
|
||||
}, {
|
||||
accessorKey: 'email',
|
||||
header: 'Email'
|
||||
}, {
|
||||
accessorKey: 'amount',
|
||||
header: () => h('div', { class: 'text-right' }, 'Amount'),
|
||||
cell: ({ row }) => {
|
||||
const amount = Number.parseFloat(row.getValue('amount'))
|
||||
const formatted = new Intl.NumberFormat('en-US', {
|
||||
style: 'currency',
|
||||
currency: 'EUR'
|
||||
}).format(amount)
|
||||
return h('div', { class: 'text-right font-medium' }, formatted)
|
||||
}
|
||||
}]
|
||||
|
||||
useSortable('.my-table-tbody', data, {
|
||||
animation: 150
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-full">
|
||||
<UTable
|
||||
ref="table"
|
||||
:data="data"
|
||||
:columns="columns"
|
||||
:ui="{
|
||||
tbody: 'my-table-tbody'
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -265,7 +265,7 @@ function randomize() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex-1 divide-y divide-(--ui-border-accented) w-full">
|
||||
<div class="flex-1 divide-y divide-accented w-full">
|
||||
<div class="flex items-center gap-2 px-4 py-3.5 overflow-x-auto">
|
||||
<UInput
|
||||
:model-value="(table?.tableApi?.getColumn('email')?.getFilterValue() as string)"
|
||||
@@ -313,7 +313,7 @@ function randomize() {
|
||||
</template>
|
||||
</UTable>
|
||||
|
||||
<div class="px-4 py-3.5 text-sm text-(--ui-text-muted)">
|
||||
<div class="px-4 py-3.5 text-sm text-muted">
|
||||
{{ table?.tableApi?.getFilteredSelectedRowModel().rows.length || 0 }} of
|
||||
{{ table?.tableApi?.getFilteredRowModel().rows.length || 0 }} row(s) selected.
|
||||
</div>
|
||||
|
||||
@@ -36,7 +36,7 @@ const columns: TableColumn<User>[] = [{
|
||||
size: 'lg'
|
||||
}),
|
||||
h('div', undefined, [
|
||||
h('p', { class: 'font-medium text-(--ui-text-highlighted)' }, row.original.name),
|
||||
h('p', { class: 'font-medium text-highlighted' }, row.original.name),
|
||||
h('p', { class: '' }, `@${row.original.username}`)
|
||||
])
|
||||
])
|
||||
|
||||
@@ -95,7 +95,7 @@ const globalFilter = ref('45')
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col flex-1 w-full">
|
||||
<div class="flex px-4 py-3.5 border-b border-(--ui-border-accented)">
|
||||
<div class="flex px-4 py-3.5 border-b border-accented">
|
||||
<UInput
|
||||
v-model="globalFilter"
|
||||
class="max-w-sm"
|
||||
|
||||
@@ -0,0 +1,203 @@
|
||||
<script setup lang="ts">
|
||||
import { h, resolveComponent } from 'vue'
|
||||
import type { TableColumn } from '@nuxt/ui'
|
||||
import { getGroupedRowModel, type GroupingOptions } from '@tanstack/vue-table'
|
||||
|
||||
const UBadge = resolveComponent('UBadge')
|
||||
|
||||
type Account = {
|
||||
id: string
|
||||
name: string
|
||||
}
|
||||
|
||||
type PaymentStatus = 'paid' | 'failed' | 'refunded'
|
||||
|
||||
type Payment = {
|
||||
id: string
|
||||
date: string
|
||||
status: PaymentStatus
|
||||
email: string
|
||||
amount: number
|
||||
account: Account
|
||||
}
|
||||
|
||||
const getColorByStatus = (status: PaymentStatus) => {
|
||||
return {
|
||||
paid: 'success',
|
||||
failed: 'error',
|
||||
refunded: 'neutral'
|
||||
}[status]
|
||||
}
|
||||
|
||||
const data = ref<Payment[]>([
|
||||
{
|
||||
id: '4600',
|
||||
date: '2024-03-11T15:30:00',
|
||||
status: 'paid',
|
||||
email: 'james.anderson@example.com',
|
||||
amount: 594,
|
||||
account: {
|
||||
id: '1',
|
||||
name: 'Account 1'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: '4599',
|
||||
date: '2024-03-11T10:10:00',
|
||||
status: 'failed',
|
||||
email: 'mia.white@example.com',
|
||||
amount: 276,
|
||||
account: {
|
||||
id: '2',
|
||||
name: 'Account 2'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: '4598',
|
||||
date: '2024-03-11T08:50:00',
|
||||
status: 'refunded',
|
||||
email: 'william.brown@example.com',
|
||||
amount: 315,
|
||||
account: {
|
||||
id: '1',
|
||||
name: 'Account 1'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: '4597',
|
||||
date: '2024-03-10T19:45:00',
|
||||
status: 'paid',
|
||||
email: 'emma.davis@example.com',
|
||||
amount: 529,
|
||||
account: {
|
||||
id: '2',
|
||||
name: 'Account 2'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: '4596',
|
||||
date: '2024-03-10T15:55:00',
|
||||
status: 'paid',
|
||||
email: 'ethan.harris@example.com',
|
||||
amount: 639,
|
||||
account: {
|
||||
id: '1',
|
||||
name: 'Account 1'
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
const columns: TableColumn<Payment>[] = [
|
||||
{
|
||||
id: 'title',
|
||||
header: 'Item'
|
||||
},
|
||||
{
|
||||
id: 'account_id',
|
||||
accessorKey: 'account.id'
|
||||
},
|
||||
{
|
||||
accessorKey: 'id',
|
||||
header: '#',
|
||||
cell: ({ row }) =>
|
||||
row.getIsGrouped()
|
||||
? `${row.getValue('id')} records`
|
||||
: `#${row.getValue('id')}`,
|
||||
aggregationFn: 'count'
|
||||
},
|
||||
{
|
||||
accessorKey: 'date',
|
||||
header: 'Date',
|
||||
cell: ({ row }) => {
|
||||
return new Date(row.getValue('date')).toLocaleString('en-US', {
|
||||
day: 'numeric',
|
||||
month: 'short',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
hour12: false
|
||||
})
|
||||
},
|
||||
aggregationFn: 'max'
|
||||
},
|
||||
{
|
||||
accessorKey: 'status',
|
||||
header: 'Status'
|
||||
},
|
||||
{
|
||||
accessorKey: 'email',
|
||||
header: 'Email',
|
||||
meta: {
|
||||
class: {
|
||||
td: 'w-full'
|
||||
}
|
||||
},
|
||||
cell: ({ row }) =>
|
||||
row.getIsGrouped()
|
||||
? `${row.getValue('email')} customers`
|
||||
: row.getValue('email'),
|
||||
aggregationFn: 'uniqueCount'
|
||||
},
|
||||
{
|
||||
accessorKey: 'amount',
|
||||
header: () => h('div', { class: 'text-right' }, 'Amount'),
|
||||
cell: ({ row }) => {
|
||||
const amount = Number.parseFloat(row.getValue('amount'))
|
||||
|
||||
const formatted = new Intl.NumberFormat('en-US', {
|
||||
style: 'currency',
|
||||
currency: 'EUR'
|
||||
}).format(amount)
|
||||
|
||||
return h('div', { class: 'text-right font-medium' }, formatted)
|
||||
},
|
||||
aggregationFn: 'sum'
|
||||
}
|
||||
]
|
||||
|
||||
const grouping_options = ref<GroupingOptions>({
|
||||
groupedColumnMode: 'remove',
|
||||
getGroupedRowModel: getGroupedRowModel()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UTable
|
||||
:data="data"
|
||||
:columns="columns"
|
||||
:grouping="['account_id', 'status']"
|
||||
:grouping-options="grouping_options"
|
||||
:ui="{
|
||||
root: 'min-w-full',
|
||||
td: 'empty:p-0' // helps with the colspaned row added for expand slot
|
||||
}"
|
||||
>
|
||||
<template #title-cell="{ row }">
|
||||
<div v-if="row.getIsGrouped()" class="flex items-center">
|
||||
<span
|
||||
class="inline-block"
|
||||
:style="{ width: `calc(${row.depth} * 1rem)` }"
|
||||
/>
|
||||
|
||||
<UButton
|
||||
variant="outline"
|
||||
color="neutral"
|
||||
class="mr-2"
|
||||
size="xs"
|
||||
:icon="row.getIsExpanded() ? 'i-lucide-minus' : 'i-lucide-plus'"
|
||||
@click="row.toggleExpanded()"
|
||||
/>
|
||||
<strong v-if="row.groupingColumnId === 'account_id'">{{
|
||||
row.original.account.name
|
||||
}}</strong>
|
||||
<UBadge
|
||||
v-else-if="row.groupingColumnId === 'status'"
|
||||
:color="getColorByStatus(row.original.status)"
|
||||
class="capitalize"
|
||||
variant="subtle"
|
||||
>
|
||||
{{ row.original.status }}
|
||||
</UBadge>
|
||||
</div>
|
||||
</template>
|
||||
</UTable>
|
||||
</template>
|
||||
@@ -0,0 +1,88 @@
|
||||
<script setup lang="ts">
|
||||
import type { TableColumn } from '@nuxt/ui'
|
||||
import { useInfiniteScroll } from '@vueuse/core'
|
||||
|
||||
const UAvatar = resolveComponent('UAvatar')
|
||||
|
||||
type User = {
|
||||
id: number
|
||||
firstName: string
|
||||
username: string
|
||||
email: string
|
||||
image: string
|
||||
}
|
||||
|
||||
type UserResponse = {
|
||||
users: User[]
|
||||
total: number
|
||||
skip: number
|
||||
limit: number
|
||||
}
|
||||
|
||||
const skip = ref(0)
|
||||
|
||||
const { data, status, execute } = await useFetch('https://dummyjson.com/users?limit=10&select=firstName,username,email,image', {
|
||||
key: 'table-users-infinite-scroll',
|
||||
params: { skip },
|
||||
transform: (data?: UserResponse) => {
|
||||
return data?.users
|
||||
},
|
||||
lazy: true,
|
||||
immediate: false
|
||||
})
|
||||
|
||||
const columns: TableColumn<User>[] = [{
|
||||
accessorKey: 'id',
|
||||
header: 'ID'
|
||||
}, {
|
||||
accessorKey: 'image',
|
||||
header: 'Avatar',
|
||||
cell: ({ row }) => h(UAvatar, { src: row.original.image })
|
||||
}, {
|
||||
accessorKey: 'firstName',
|
||||
header: 'First name'
|
||||
}, {
|
||||
accessorKey: 'email',
|
||||
header: 'Email'
|
||||
}, {
|
||||
accessorKey: 'username',
|
||||
header: 'Username'
|
||||
}]
|
||||
|
||||
const users = ref<User[]>([])
|
||||
|
||||
watch(data, () => {
|
||||
users.value = [
|
||||
...users.value,
|
||||
...(data.value || [])
|
||||
]
|
||||
})
|
||||
|
||||
execute()
|
||||
|
||||
const table = useTemplateRef<ComponentPublicInstance>('table')
|
||||
|
||||
onMounted(() => {
|
||||
useInfiniteScroll(table.value?.$el, () => {
|
||||
skip.value += 10
|
||||
}, {
|
||||
distance: 200,
|
||||
canLoadMore: () => {
|
||||
return status.value !== 'pending'
|
||||
}
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-full">
|
||||
<UTable
|
||||
ref="table"
|
||||
:data="users"
|
||||
:columns="columns"
|
||||
:loading="status === 'pending'"
|
||||
sticky
|
||||
class="flex-1 h-80"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -162,7 +162,7 @@ const pagination = ref({
|
||||
class="flex-1"
|
||||
/>
|
||||
|
||||
<div class="flex justify-center border-t border-(--ui-border) pt-4">
|
||||
<div class="flex justify-center border-t border-default pt-4">
|
||||
<UPagination
|
||||
:default-page="(table?.tableApi?.getState().pagination.pageIndex || 0) + 1"
|
||||
:items-per-page="table?.tableApi?.getState().pagination.pageSize"
|
||||
|
||||
@@ -112,7 +112,7 @@ const expanded = ref({ 1: true })
|
||||
v-model:expanded="expanded"
|
||||
:data="data"
|
||||
:columns="columns"
|
||||
:ui="{ tr: 'data-[expanded=true]:bg-(--ui-bg-elevated)/50' }"
|
||||
:ui="{ tr: 'data-[expanded=true]:bg-elevated/50' }"
|
||||
class="flex-1"
|
||||
>
|
||||
<template #expanded="{ row }">
|
||||
|
||||
@@ -122,7 +122,7 @@ function onSelect(row: TableRow<Payment>, e?: Event) {
|
||||
@select="onSelect"
|
||||
/>
|
||||
|
||||
<div class="px-4 py-3.5 border-t border-[var(--ui-border-accented)] text-sm text-[var(--ui-text-muted)]">
|
||||
<div class="px-4 py-3.5 border-t border-accented text-sm text-muted">
|
||||
{{ table?.tableApi?.getFilteredSelectedRowModel().rows.length || 0 }} of
|
||||
{{ table?.tableApi?.getFilteredRowModel().rows.length || 0 }} row(s) selected.
|
||||
</div>
|
||||
|
||||
@@ -113,7 +113,7 @@ const rowSelection = ref({ 1: true })
|
||||
:columns="columns"
|
||||
/>
|
||||
|
||||
<div class="px-4 py-3.5 border-t border-(--ui-border-accented) text-sm text-(--ui-text-muted)">
|
||||
<div class="px-4 py-3.5 border-t border-accented text-sm text-muted">
|
||||
{{ table?.tableApi?.getFilteredSelectedRowModel().rows.length || 0 }} of
|
||||
{{ table?.tableApi?.getFilteredRowModel().rows.length || 0 }} row(s) selected.
|
||||
</div>
|
||||
|
||||
@@ -97,7 +97,7 @@ function getDropdownActions(user: User): DropdownMenuItem[][] {
|
||||
<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`" />
|
||||
<div>
|
||||
<p class="font-medium text-(--ui-text-highlighted)">
|
||||
<p class="font-medium text-highlighted">
|
||||
{{ row.original.name }}
|
||||
</p>
|
||||
<p>
|
||||
|
||||
@@ -26,9 +26,9 @@ const state = reactive({
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UTabs :items="items" variant="link" class="gap-4 w-full" :ui="{ trigger: 'flex-1' }">
|
||||
<UTabs :items="items" variant="link" class="gap-4 w-full" :ui="{ trigger: 'grow' }">
|
||||
<template #account="{ item }">
|
||||
<p class="text-(--ui-text-muted) mb-4">
|
||||
<p class="text-muted mb-4">
|
||||
{{ item.description }}
|
||||
</p>
|
||||
|
||||
@@ -45,7 +45,7 @@ const state = reactive({
|
||||
</template>
|
||||
|
||||
<template #password="{ item }">
|
||||
<p class="text-(--ui-text-muted) mb-4">
|
||||
<p class="text-muted mb-4">
|
||||
{{ item.description }}
|
||||
</p>
|
||||
|
||||
|
||||
@@ -1,22 +1,32 @@
|
||||
<script setup lang="ts">
|
||||
import type { TabsItem } from '@nuxt/ui'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
const items: TabsItem[] = [
|
||||
{
|
||||
label: 'Account'
|
||||
label: 'Account',
|
||||
value: 'account'
|
||||
},
|
||||
{
|
||||
label: 'Password'
|
||||
label: 'Password',
|
||||
value: 'password'
|
||||
}
|
||||
]
|
||||
|
||||
const active = ref('0')
|
||||
|
||||
// Note: This is for demonstration purposes only. Don't do this at home.
|
||||
onMounted(() => {
|
||||
setInterval(() => {
|
||||
active.value = String((Number(active.value) + 1) % items.length)
|
||||
}, 2000)
|
||||
const active = computed({
|
||||
get() {
|
||||
return (route.query.tab as string) || 'account'
|
||||
},
|
||||
set(tab) {
|
||||
// Hash is specified here to prevent the page from scrolling to the top
|
||||
router.push({
|
||||
path: '/components/tabs',
|
||||
query: { tab },
|
||||
hash: '#control-active-item'
|
||||
})
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -7,10 +7,10 @@ const appConfig = useAppConfig()
|
||||
<UFormField
|
||||
label="toaster.duration"
|
||||
size="sm"
|
||||
class="inline-flex ring ring-(--ui-border-accented) rounded-(--ui-radius)"
|
||||
class="inline-flex ring ring-accented rounded-sm"
|
||||
:ui="{
|
||||
wrapper: 'bg-(--ui-bg-elevated)/50 rounded-l-(--ui-radius) flex border-r border-(--ui-border-accented)',
|
||||
label: 'text-(--ui-text-muted) px-2 py-1.5',
|
||||
wrapper: 'bg-elevated/50 rounded-l-sm flex border-r border-accented',
|
||||
label: 'text-muted px-2 py-1.5',
|
||||
container: 'mt-0'
|
||||
}"
|
||||
>
|
||||
@@ -18,7 +18,7 @@ const appConfig = useAppConfig()
|
||||
v-model="appConfig.toaster.duration"
|
||||
color="neutral"
|
||||
variant="soft"
|
||||
:ui="{ base: 'rounded-(--ui-radius) rounded-l-none min-w-12' }"
|
||||
:ui="{ base: 'rounded-sm rounded-l-none min-w-12' }"
|
||||
/>
|
||||
</UFormField>
|
||||
</div>
|
||||
|
||||
@@ -7,10 +7,10 @@ const appConfig = useAppConfig()
|
||||
<UFormField
|
||||
label="toaster.expand"
|
||||
size="sm"
|
||||
class="inline-flex ring ring-(--ui-border-accented) rounded-(--ui-radius)"
|
||||
class="inline-flex ring ring-accented rounded-sm"
|
||||
:ui="{
|
||||
wrapper: 'bg-(--ui-bg-elevated)/50 rounded-l-(--ui-radius) flex border-r border-(--ui-border-accented)',
|
||||
label: 'text-(--ui-text-muted) px-2 py-1.5',
|
||||
wrapper: 'bg-elevated/50 rounded-l-sm flex border-r border-accented',
|
||||
label: 'text-muted px-2 py-1.5',
|
||||
container: 'mt-0'
|
||||
}"
|
||||
>
|
||||
@@ -19,7 +19,7 @@ const appConfig = useAppConfig()
|
||||
:items="[true, false]"
|
||||
color="neutral"
|
||||
variant="soft"
|
||||
class="rounded-(--ui-radius) rounded-l-none min-w-12"
|
||||
class="rounded-sm rounded-l-none min-w-12"
|
||||
:search-input="false"
|
||||
/>
|
||||
</UFormField>
|
||||
|
||||
@@ -10,10 +10,10 @@ const appConfig = useAppConfig()
|
||||
<UFormField
|
||||
label="toaster.position"
|
||||
size="sm"
|
||||
class="inline-flex ring ring-(--ui-border-accented) rounded-(--ui-radius)"
|
||||
class="inline-flex ring ring-accented rounded-sm"
|
||||
:ui="{
|
||||
wrapper: 'bg-(--ui-bg-elevated)/50 rounded-l-(--ui-radius) flex border-r border-(--ui-border-accented)',
|
||||
label: 'text-(--ui-text-muted) px-2 py-1.5',
|
||||
wrapper: 'bg-elevated/50 rounded-l-sm flex border-r border-accented',
|
||||
label: 'text-muted px-2 py-1.5',
|
||||
container: 'mt-0'
|
||||
}"
|
||||
>
|
||||
@@ -22,7 +22,7 @@ const appConfig = useAppConfig()
|
||||
:items="positions"
|
||||
color="neutral"
|
||||
variant="soft"
|
||||
class="rounded-(--ui-radius) rounded-l-none min-w-12"
|
||||
class="rounded-sm rounded-l-none min-w-12"
|
||||
:search-input="false"
|
||||
/>
|
||||
</UFormField>
|
||||
|
||||
@@ -20,7 +20,7 @@ const { width } = useElementSize(el)
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="isolate rounded-full relative circle w-full aspect-[1/1] p-8 sm:p-12 md:p-14 lg:p-10 xl:p-16 before:absolute before:inset-px before:bg-(--ui-bg) before:rounded-full z-(--level)"
|
||||
class="isolate rounded-full relative circle w-full aspect-[1/1] p-8 sm:p-12 md:p-14 lg:p-10 xl:p-16 before:absolute before:inset-px before:bg-default before:rounded-full z-(--level)"
|
||||
:class="{ 'animation-paused': paused }"
|
||||
:style="{
|
||||
'--duration': `${((level + 1) * 8)}s`,
|
||||
@@ -65,7 +65,7 @@ const { width } = useElementSize(el)
|
||||
:src="`https://ipx.nuxt.com/s_56x56/gh_avatar/${contributor.username}`"
|
||||
:srcset="`https://ipx.nuxt.com/s_112x112/gh_avatar/${contributor.username} 2x`"
|
||||
:alt="contributor.username"
|
||||
class="ring-2 ring-(--ui-border) lg:hover:ring-(--ui-border-inverted) transition rounded-full size-7"
|
||||
class="ring-2 ring-default lg:hover:ring-inverted transition rounded-full size-7"
|
||||
loading="lazy"
|
||||
>
|
||||
</NuxtLink>
|
||||
|
||||
@@ -69,7 +69,7 @@ function setBlackAsPrimary(value: boolean) {
|
||||
:variant="open ? 'soft' : 'ghost'"
|
||||
square
|
||||
aria-label="Color picker"
|
||||
:ui="{ leadingIcon: 'text-(--ui-primary)' }"
|
||||
:ui="{ leadingIcon: 'text-primary' }"
|
||||
/>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -18,8 +18,8 @@ const slots = defineSlots<{
|
||||
variant="outline"
|
||||
:icon="icon"
|
||||
:label="label"
|
||||
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="capitalize ring-default rounded-sm text-[11px]"
|
||||
:class="[selected ? 'bg-elevated' : 'hover:bg-elevated/50']"
|
||||
>
|
||||
<template v-if="chip || !!slots.leading" #leading>
|
||||
<slot name="leading">
|
||||
|
||||
54
docs/app/composables/useFaviconFromTheme.ts
Normal file
54
docs/app/composables/useFaviconFromTheme.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { onMounted, watch } from 'vue'
|
||||
import FaviconSvg from 'public/icon.svg?raw'
|
||||
|
||||
export function useFaviconFromTheme() {
|
||||
const colorMode = useColorMode()
|
||||
|
||||
function generateFaviconSvg(color: string) {
|
||||
const parser = new DOMParser()
|
||||
const doc = parser.parseFromString(FaviconSvg, 'image/svg+xml')
|
||||
const svg = doc.documentElement
|
||||
|
||||
svg.querySelectorAll('path').forEach((path) => {
|
||||
path.setAttribute('fill', color)
|
||||
})
|
||||
|
||||
return new XMLSerializer().serializeToString(svg)
|
||||
}
|
||||
|
||||
function updateFavicon() {
|
||||
const root = document.documentElement
|
||||
const color = getComputedStyle(root).getPropertyValue('--ui-primary').trim() || '#00DC82'
|
||||
|
||||
const svg = generateFaviconSvg(color)
|
||||
const encoded = `data:image/svg+xml,${encodeURIComponent(svg)}`
|
||||
|
||||
useFavicon(encoded)
|
||||
}
|
||||
|
||||
function setupMutationObserver() {
|
||||
const styleTag = document.getElementById('nuxt-ui-colors')
|
||||
if (!styleTag) return
|
||||
|
||||
const observer = new MutationObserver(() => {
|
||||
updateFavicon()
|
||||
})
|
||||
|
||||
observer.observe(styleTag, {
|
||||
characterData: true,
|
||||
subtree: true,
|
||||
childList: true
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
watch(colorMode, () => {
|
||||
updateFavicon()
|
||||
}, {
|
||||
immediate: true,
|
||||
flush: 'post'
|
||||
})
|
||||
|
||||
setupMutationObserver()
|
||||
})
|
||||
}
|
||||
@@ -98,7 +98,7 @@ export function useLinks() {
|
||||
label: 'Raycast Extension',
|
||||
description: 'Access Nuxt UI components without leaving your editor.',
|
||||
icon: 'i-simple-icons-raycast',
|
||||
to: 'https://www.raycast.com/HugoRCD/nuxt-ui',
|
||||
to: 'https://www.raycast.com/HugoRCD/nuxt',
|
||||
target: '_blank'
|
||||
}, {
|
||||
label: 'Figma to Code',
|
||||
|
||||
@@ -26,7 +26,7 @@ useHead({
|
||||
{ key: 'theme-color', name: 'theme-color', content: color }
|
||||
],
|
||||
link: [
|
||||
{ rel: 'icon', type: 'image/svg+xml', href: '/icon.svg' }
|
||||
// { rel: 'icon', type: 'image/svg+xml', href: '/icon.svg' }
|
||||
],
|
||||
style: [
|
||||
{ innerHTML: radius, id: 'nuxt-ui-radius', tagPriority: -2 },
|
||||
@@ -47,6 +47,8 @@ useServerSeoMeta({
|
||||
twitterCard: 'summary_large_image'
|
||||
})
|
||||
|
||||
useFaviconFromTheme()
|
||||
|
||||
const { frameworks, modules } = useSharedData()
|
||||
const { mappedNavigation, filteredNavigation } = useContentNavigation(navigation)
|
||||
|
||||
@@ -57,7 +59,7 @@ provide('navigation', mappedNavigation)
|
||||
<UApp>
|
||||
<NuxtLoadingIndicator color="#FFF" />
|
||||
|
||||
<Banner />
|
||||
<!-- <Banner /> -->
|
||||
|
||||
<Header :links="links" />
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ const navigation = inject<Ref<ContentNavigationItem[]>>('navigation')
|
||||
<span class="inline-flex items-center gap-0.5">
|
||||
{{ link.title }}
|
||||
|
||||
<sup v-if="link.module === 'ui-pro'" class="text-[8px] font-medium text-(--ui-primary)">PRO</sup>
|
||||
<sup v-if="link.module === 'ui-pro'" class="text-[8px] font-medium text-primary">PRO</sup>
|
||||
</span>
|
||||
</template>
|
||||
</UContentNavigation>
|
||||
|
||||
@@ -101,7 +101,7 @@ design_system:
|
||||
@import "@nuxt/ui";
|
||||
|
||||
:root {
|
||||
--ui-radius: var(--radius-sm);
|
||||
--ui-radius: 0.25rem;
|
||||
--ui-container: 90rem;
|
||||
--ui-bg: var(--ui-color-neutral-50);
|
||||
--ui-text: var(--ui-color-neutral-900);
|
||||
|
||||
@@ -65,13 +65,17 @@ if (!import.meta.prerender) {
|
||||
})
|
||||
}
|
||||
|
||||
const type = page.value?.path.includes('components') ? 'Vue Component ' : page.value?.path.includes('composables') ? 'Vue Composable ' : ''
|
||||
const title = page.value?.navigation?.title ? page.value.navigation.title : page.value?.title
|
||||
const prefix = page.value?.path.includes('components') || page.value?.path.includes('composables') ? 'Vue ' : ''
|
||||
const suffix = page.value?.path.includes('components') ? 'Component ' : page.value?.path.includes('composables') ? 'Composable ' : ''
|
||||
const description = page.value?.description
|
||||
|
||||
useSeoMeta({
|
||||
titleTemplate: `%s ${type}- Nuxt UI ${page.value.module === 'ui-pro' ? 'Pro' : ''} ${page.value.framework === 'vue' ? ' for Vue' : ''}`,
|
||||
title: page.value.navigation?.title ? page.value.navigation.title : page.value.title,
|
||||
ogTitle: `${page.value.navigation?.title ? page.value.navigation.title : page.value.title} ${type}- Nuxt UI ${page.value.module === 'ui-pro' ? 'Pro' : ''} ${page.value.framework === 'vue' ? ' for Vue' : ''}`,
|
||||
description: page.value.description,
|
||||
ogDescription: page.value.description
|
||||
titleTemplate: `${prefix}%s ${suffix}- Nuxt UI ${page.value?.module === 'ui-pro' ? 'Pro' : ''} ${page.value?.framework === 'vue' ? ' for Vue' : ''}`,
|
||||
title,
|
||||
ogTitle: `${prefix}${title} ${suffix}- Nuxt UI ${page.value?.module === 'ui-pro' ? 'Pro' : ''} ${page.value?.framework === 'vue' ? ' for Vue' : ''}`,
|
||||
description,
|
||||
ogDescription: description
|
||||
})
|
||||
|
||||
if (route.path.startsWith('/components')) {
|
||||
@@ -130,7 +134,7 @@ const communityLinks = computed(() => [{
|
||||
</template>
|
||||
|
||||
<template #title>
|
||||
{{ page.title }}<sup v-if="page.module === 'ui-pro'" class="ml-1 text-xs align-super font-medium text-(--ui-primary)">PRO</sup>
|
||||
{{ page.title }}<sup v-if="page.module === 'ui-pro'" class="ml-1 text-xs align-super font-medium text-primary">PRO</sup>
|
||||
</template>
|
||||
|
||||
<template #description>
|
||||
|
||||
@@ -82,7 +82,7 @@ onMounted(() => {
|
||||
:ui="{ title: 'text-balance', container: 'relative' }"
|
||||
>
|
||||
<template #top>
|
||||
<div class="absolute z-[-1] rounded-full bg-(--ui-primary) blur-[300px] size-60 sm:size-80 transform -translate-x-1/2 left-1/2 -translate-y-80" />
|
||||
<div class="absolute z-[-1] rounded-full bg-primary blur-[300px] size-60 sm:size-80 transform -translate-x-1/2 left-1/2 -translate-y-80" />
|
||||
</template>
|
||||
|
||||
<template #headline>
|
||||
@@ -97,7 +97,7 @@ onMounted(() => {
|
||||
/>
|
||||
</template>
|
||||
<template #title>
|
||||
Build beautiful UI with <span class="text-(--ui-primary)">{{ components!.length }}+</span> powerful components
|
||||
Build beautiful UI with <span class="text-primary">{{ components!.length }}+</span> powerful components
|
||||
</template>
|
||||
|
||||
<template #links>
|
||||
@@ -121,22 +121,22 @@ onMounted(() => {
|
||||
|
||||
<LazyStarsBg />
|
||||
|
||||
<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-default inset-0 mx-4 sm:mx-6 lg:mx-8" />
|
||||
</UPageHero>
|
||||
|
||||
<div v-for="category in categories" :key="category.id">
|
||||
<div data-track-sticky class="group mb-4 sm:mb-6 lg:mb-8 sticky top-[calc(var(--ui-header-height)-1px)] bg-(--ui-bg)/75 backdrop-blur z-[1]">
|
||||
<div class="relative border-y border-(--ui-border) py-4 sm:not-group-[[data-stuck]]:py-6 lg:not-group-[[data-stuck]]:py-8 transition-all duration-300">
|
||||
<div data-track-sticky class="group mb-4 sm:mb-6 lg:mb-8 sticky top-[calc(var(--ui-header-height)-1px)] bg-default/75 backdrop-blur z-[1]">
|
||||
<div class="relative border-y border-default py-4 sm:not-group-[[data-stuck]]:py-6 lg:not-group-[[data-stuck]]:py-8 transition-all duration-300">
|
||||
<UContainer>
|
||||
<h2 class="relative text-pretty font-bold text-(--ui-text-highlighted) text-base sm:not-group-[[data-stuck]]:text-xl lg:not-group-[[data-stuck]]:text-2xl transition-all duration-300 ">
|
||||
<h2 class="relative text-pretty font-bold text-highlighted text-base sm:not-group-[[data-stuck]]:text-xl lg:not-group-[[data-stuck]]:text-2xl transition-all duration-300 ">
|
||||
<a :href="`#${category.id}`" class="group lg:not-group-[[data-stuck]]:ps-2 lg:not-group-[[data-stuck]]:-ms-2">
|
||||
<span class="absolute -ms-8 top-1 opacity-0 group-hover:opacity-100 group-focus:opacity-100 p-1 bg-(--ui-bg-elevated) hover:text-(--ui-primary) rounded-[calc(var(--ui-radius)*1.5)] hidden lg:not-group-[[data-stuck]]:flex text-(--ui-text-muted) transition">
|
||||
<span class="absolute -ms-8 top-1 opacity-0 group-hover:opacity-100 group-focus:opacity-100 p-1 bg-elevated hover:text-primary rounded-md hidden lg:not-group-[[data-stuck]]:flex text-muted transition">
|
||||
<UIcon name="i-lucide-hash" class="size-4 shrink-0" />
|
||||
</span>
|
||||
{{ category.title }}
|
||||
</a>
|
||||
</h2>
|
||||
<p class="text-pretty text-(--ui-text-muted) text-sm sm:not-group-[[data-stuck]]:text-base lg:not-group-[[data-stuck]]:text-lg mt-1 sm:not-group-[[data-stuck]]:mt-2 line-clamp-1 transition-all duration-300">
|
||||
<p class="text-pretty text-muted text-sm sm:not-group-[[data-stuck]]:text-base lg:not-group-[[data-stuck]]:text-lg mt-1 sm:not-group-[[data-stuck]]:mt-2 line-clamp-1 transition-all duration-300">
|
||||
{{ category.description }}
|
||||
</p>
|
||||
</UContainer>
|
||||
@@ -157,11 +157,11 @@ onMounted(() => {
|
||||
<template #title>
|
||||
<div class="flex items-center gap-0.5">
|
||||
<span>{{ component.title }}</span>
|
||||
<sup v-if="component.module === 'ui-pro'" class="text-[8px] font-medium text-(--ui-primary)">PRO</sup>
|
||||
<sup v-if="component.module === 'ui-pro'" class="text-[8px] font-medium text-primary">PRO</sup>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="rounded-[calc(var(--ui-radius)*1.5)] border border-(--ui-border-muted) overflow-hidden aspect-[16/9]">
|
||||
<div class="rounded-md border border-muted overflow-hidden aspect-[16/9]">
|
||||
<UColorModeImage
|
||||
:light="`${component.path.replace('/components/', '/components/light/')}.png`"
|
||||
:dark="`${component.path.replace('/components/', '/components/dark/')}.png`"
|
||||
|
||||
@@ -1,8 +1,20 @@
|
||||
<script setup lang="ts">
|
||||
const route = useRoute()
|
||||
const colorMode = useColorMode()
|
||||
const appConfig = useAppConfig()
|
||||
|
||||
const name = route.params.slug?.[0]
|
||||
|
||||
if (route.query.theme) {
|
||||
colorMode.preference = route.query.theme === 'light' ? 'light' : 'dark'
|
||||
}
|
||||
if (route.query.neutral) {
|
||||
appConfig.ui.colors.neutral = route.query.neutral as string
|
||||
}
|
||||
if (route.query.primary) {
|
||||
appConfig.ui.colors.primary = route.query.primary as string
|
||||
}
|
||||
|
||||
const width = computed(() => route.query.width && Number.parseInt(route.query.width as string) > 0 ? `${Number.parseInt(route.query.width as string) - 2}px` : '864px')
|
||||
</script>
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ features1:
|
||||
description: Start with essential components, or unlock Pro for complete blocks and templates.
|
||||
icon: i-lucide-files
|
||||
cta1:
|
||||
title: Everything you need in a [single file]{class="text-(--ui-primary)"}.
|
||||
title: Everything you need in a [single file]{class="text-primary"}.
|
||||
description: Design and development in perfect sync with our [Free](https://www.figma.com/community/file/1288455405058138934/nuxt-ui-v3-official-design-kit-free) and Pro files. Developers can implement designs faster, while designers work with production-ready components.
|
||||
section1:
|
||||
title: Customize in a few clicks to fit your needs
|
||||
@@ -181,7 +181,7 @@ pricing:
|
||||
# discount: $119
|
||||
billing_period: one-time payment
|
||||
billing_cycle: plus local taxes
|
||||
class: bg-(--ui-bg-elevated)/50
|
||||
class: bg-elevated/50
|
||||
features:
|
||||
- '**1 Designer**'
|
||||
- Nuxt UI & Nuxt UI Pro Components
|
||||
@@ -203,7 +203,7 @@ pricing:
|
||||
# discount: $279
|
||||
billing_period: one-time payment
|
||||
billing_cycle: plus local taxes
|
||||
class: bg-(--ui-bg-elevated)/50
|
||||
class: bg-elevated/50
|
||||
features:
|
||||
- '**Up to 20 Designers**'
|
||||
- Nuxt UI & Nuxt UI Pro Components
|
||||
|
||||
@@ -24,32 +24,41 @@ onMounted(async () => {
|
||||
const nuxtWordPosition = document.querySelector('#nuxt')?.getBoundingClientRect()
|
||||
const initialScrollX = window.scrollX
|
||||
const initialScrollY = window.scrollY
|
||||
if (figmaWordPosition && nuxtWordPosition) {
|
||||
animate('#cursor1', { left: Math.round(Math.random() * window.outerWidth), top: Math.round(Math.random() * window.outerHeight) }, { duration: 0.1 })
|
||||
.then(() => animate('#cursor1', { opacity: 1 }, { duration: 0.3 }))
|
||||
.then(() => {
|
||||
return animate('#cursor1', {
|
||||
left: Math.round(figmaWordPosition.left + initialScrollX + figmaWordPosition.width / 2),
|
||||
top: Math.round(figmaWordPosition.top + initialScrollY - figmaWordPosition.height / 4)
|
||||
}, { duration: 1.5, delay: 0.2, ease: 'easeInOut' })
|
||||
})
|
||||
.then(() => animate('#cursor1', { scale: 0.8 }, { duration: 0.1, ease: 'easeOut' }))
|
||||
.then(() => animate('#cursor1', { scale: 1 }, { duration: 0.1, ease: 'easeOut' }))
|
||||
.then(() => animate('#figma', { color: 'var(--ui-info)' }, { duration: 0.3, ease: 'easeOut' }))
|
||||
.then(() => animate('#cursor1', { left: Math.round(figmaWordPosition.left + initialScrollX + figmaWordPosition.width), top: Math.round(figmaWordPosition.top + initialScrollY) }, { duration: 0.6, ease: 'easeInOut' }))
|
||||
|
||||
animate('#cursor2', { left: Math.round(Math.random() * window.outerWidth), top: Math.round(Math.random() * window.outerHeight) }, { duration: 0.1, delay: 0.6 })
|
||||
.then(() => animate('#cursor2', { opacity: 1 }, { duration: 0.3 }))
|
||||
.then(() => {
|
||||
return animate('#cursor2', {
|
||||
left: Math.round(nuxtWordPosition.left + initialScrollX + nuxtWordPosition.width / 2),
|
||||
top: Math.round(nuxtWordPosition.top + initialScrollY - nuxtWordPosition.height / 4)
|
||||
}, { duration: 1.5, delay: 0.2, ease: 'easeInOut' })
|
||||
})
|
||||
.then(() => animate('#cursor2', { scale: 0.8 }, { duration: 0.1, ease: 'easeOut' }))
|
||||
.then(() => animate('#cursor2', { scale: 1 }, { duration: 0.1, ease: 'easeOut' }))
|
||||
.then(() => animate('#nuxt', { color: 'var(--ui-success)' }, { duration: 0.3, ease: 'easeOut' }))
|
||||
.then(() => animate('#cursor2', { left: Math.round(nuxtWordPosition.left + initialScrollX + nuxtWordPosition.width), top: Math.round(nuxtWordPosition.top + initialScrollY) }, { duration: 0.6, ease: 'easeInOut' }))
|
||||
if (figmaWordPosition && nuxtWordPosition) {
|
||||
const cursor1Sequence = async () => {
|
||||
await animate('#cursor1', { left: Math.round(Math.random() * window.outerWidth), top: Math.round(Math.random() * window.outerHeight) }, { duration: 0.1 }).finished
|
||||
await animate('#cursor1', { opacity: 1 }, { duration: 0.3 }).finished
|
||||
await animate('#cursor1', {
|
||||
left: Math.round(figmaWordPosition.left + initialScrollX + figmaWordPosition.width / 2),
|
||||
top: Math.round(figmaWordPosition.top + initialScrollY - figmaWordPosition.height / 4)
|
||||
}, { duration: 1.5, delay: 0.2, ease: 'easeInOut' }).finished
|
||||
await animate('#cursor1', { scale: 0.8 }, { duration: 0.1, ease: 'easeOut' }).finished
|
||||
await animate('#cursor1', { scale: 1 }, { duration: 0.1, ease: 'easeOut' }).finished
|
||||
await animate('#figma', { color: 'var(--ui-info)' }, { duration: 0.3, ease: 'easeOut' }).finished
|
||||
await animate('#cursor1', {
|
||||
left: Math.round(figmaWordPosition.left + initialScrollX + figmaWordPosition.width),
|
||||
top: Math.round(figmaWordPosition.top + initialScrollY)
|
||||
}, { duration: 0.6, ease: 'easeInOut' }).finished
|
||||
}
|
||||
|
||||
const cursor2Sequence = async () => {
|
||||
await animate('#cursor2', { left: Math.round(Math.random() * window.outerWidth), top: Math.round(Math.random() * window.outerHeight) }, { duration: 0.1, delay: 0.6 }).finished
|
||||
await animate('#cursor2', { opacity: 1 }, { duration: 0.3 }).finished
|
||||
await animate('#cursor2', {
|
||||
left: Math.round(nuxtWordPosition.left + initialScrollX + nuxtWordPosition.width / 2),
|
||||
top: Math.round(nuxtWordPosition.top + initialScrollY - nuxtWordPosition.height / 4)
|
||||
}, { duration: 1.5, delay: 0.2, ease: 'easeInOut' }).finished
|
||||
await animate('#cursor2', { scale: 0.8 }, { duration: 0.1, ease: 'easeOut' }).finished
|
||||
await animate('#cursor2', { scale: 1 }, { duration: 0.1, ease: 'easeOut' }).finished
|
||||
await animate('#nuxt', { color: 'var(--ui-success)' }, { duration: 0.3, ease: 'easeOut' }).finished
|
||||
await animate('#cursor2', {
|
||||
left: Math.round(nuxtWordPosition.left + initialScrollX + nuxtWordPosition.width),
|
||||
top: Math.round(nuxtWordPosition.top + initialScrollY)
|
||||
}, { duration: 0.6, ease: 'easeInOut' }).finished
|
||||
}
|
||||
|
||||
await Promise.all([cursor1Sequence(), cursor2Sequence()])
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@@ -57,7 +66,7 @@ onMounted(async () => {
|
||||
<template>
|
||||
<div class="relative">
|
||||
<div id="cursor1" class="absolute z-10 pointer-events-none" :style="{ opacity: 0 }">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="26" height="26" viewBox="0 0 24 24" class="absolute top-0 left-0 drop-shadow-[0_1px_2px_rgb(0,0,0,0.25)] text-white dark:text-(--ui-bg)">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="26" height="26" viewBox="0 0 24 24" class="absolute top-0 left-0 drop-shadow-[0_1px_2px_rgb(0,0,0,0.25)] text-inverted">
|
||||
<path
|
||||
fill="var(--ui-info)"
|
||||
stroke="currentColor"
|
||||
@@ -72,7 +81,7 @@ onMounted(async () => {
|
||||
</UBadge>
|
||||
</div>
|
||||
<div id="cursor2" class="absolute z-10 pointer-events-none" :style="{ opacity: 0 }">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="26" height="26" viewBox="0 0 24 24" class="absolute top-0 left-0 drop-shadow-[0_1px_2px_rgb(0,0,0,0.25)] text-white dark:text-(--ui-bg)">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="26" height="26" viewBox="0 0 24 24" class="absolute top-0 left-0 drop-shadow-[0_1px_2px_rgb(0,0,0,0.25)] text-inverted">
|
||||
<path
|
||||
fill="var(--ui-success)"
|
||||
stroke="currentColor"
|
||||
@@ -99,7 +108,7 @@ onMounted(async () => {
|
||||
<template #description>
|
||||
<MDC :value="page.hero.description" unwrap="p" cache-key="figma-hero-description" />
|
||||
</template>
|
||||
<!-- <img src="/pro/figma/nuxt-ui-figma.png" alt="Screnshot of the Nuxt UI Figma design kit" class="w-full h-auto border border-(--ui-border) border-b-0"> -->
|
||||
<!-- <img src="/pro/figma/nuxt-ui-figma.png" alt="Screnshot of the Nuxt UI Figma design kit" class="w-full h-auto border border-default border-b-0"> -->
|
||||
<div class="relative">
|
||||
<video
|
||||
ref="video"
|
||||
@@ -126,10 +135,10 @@ onMounted(async () => {
|
||||
</div>
|
||||
</div>
|
||||
<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-default inset-0 mx-4 sm:mx-6 lg:mx-8" />
|
||||
</Motion>
|
||||
</UPageHero>
|
||||
<UPageSection v-bind="page.features1" :ui="{ container: 'py-16 sm:py-16 lg:py-16', features: 'mt-0' }" class="border-y border-(--ui-border)" />
|
||||
<UPageSection v-bind="page.features1" :ui="{ container: 'py-16 sm:py-16 lg:py-16', features: 'mt-0' }" class="border-y border-default" />
|
||||
<UPageCTA
|
||||
v-if="page.cta1"
|
||||
variant="naked"
|
||||
@@ -138,7 +147,7 @@ onMounted(async () => {
|
||||
wrapper: 'grid grid-cols-1 lg:grid-cols-2',
|
||||
description: 'lg:mt-0' }"
|
||||
orientation="horizontal"
|
||||
class="rounded-none bg-gradient-to-b from-(--ui-bg-muted) to-(--ui-bg)"
|
||||
class="rounded-none bg-gradient-to-b from-elevated/50 to-default"
|
||||
>
|
||||
<template #title>
|
||||
<MDC :value="page.cta1.title" unwrap="p" cache-key="figma-cta-1-title" />
|
||||
@@ -155,7 +164,7 @@ onMounted(async () => {
|
||||
:height="item.height"
|
||||
:src="item.src"
|
||||
:alt="item.alt"
|
||||
class="w-full h-auto rounded-[calc(var(--ui-radius)*2)]"
|
||||
class="w-full h-auto rounded-lg"
|
||||
loading="lazy"
|
||||
/>
|
||||
</template>
|
||||
@@ -165,7 +174,7 @@ onMounted(async () => {
|
||||
<NuxtImg
|
||||
v-if="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-lg"
|
||||
loading="lazy"
|
||||
/>
|
||||
</UPageSection>
|
||||
@@ -173,7 +182,7 @@ onMounted(async () => {
|
||||
<NuxtImg
|
||||
v-if="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-lg"
|
||||
loading="lazy"
|
||||
/>
|
||||
</UPageSection>
|
||||
@@ -192,27 +201,27 @@ onMounted(async () => {
|
||||
<template #description>
|
||||
<MDC :value="page.section4.description" unwrap="p" cache-key="figma-section-4-description" />
|
||||
</template>
|
||||
<div aria-hidden="true" class="absolute z-[-1] border-x border-(--ui-border) inset-0 mx-4 sm:mx-6 lg:mx-8" />
|
||||
<ul class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 items-start justify-center border border-(--ui-border) border-b-0 sm:divide-x divide-y lg:divide-y-0 divide-(--ui-border)">
|
||||
<div aria-hidden="true" class="absolute z-[-1] border-x border-default inset-0 mx-4 sm:mx-6 lg:mx-8" />
|
||||
<ul class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 items-start justify-center border border-default border-b-0 sm:divide-x divide-y lg:divide-y-0 divide-default">
|
||||
<li v-for="(step, index) in page?.section4.steps" :key="step.title" class="flex flex-col gap-y-4 justify-start group h-full p-4">
|
||||
<NuxtImg
|
||||
v-if="step.image"
|
||||
v-bind="step.image"
|
||||
class="rounded-(--ui-radius)"
|
||||
class="rounded-sm"
|
||||
loading="lazy"
|
||||
/>
|
||||
<div>
|
||||
<h2 class="font-semibold inline-flex items-center gap-x-1">
|
||||
<UBadge :label="index + 1" size="sm" color="neutral" variant="subtle" class="rounded-full tabular-nums" /> {{ step.title }}
|
||||
</h2>
|
||||
<p class="text-(--ui-text-muted) text-sm">
|
||||
<p class="text-muted text-sm">
|
||||
{{ step.description }}
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</UPageSection>
|
||||
<UPageSection v-bind="page.features2" :ui="{ container: 'py-16 sm:py-16 lg:py-16', features: 'mt-0' }" class="border-y border-(--ui-border)" />
|
||||
<UPageSection v-bind="page.features2" :ui="{ container: 'py-16 sm:py-16 lg:py-16', features: 'mt-0' }" class="border-y border-default" />
|
||||
<UPageSection
|
||||
v-if="page.pricing"
|
||||
:title="page.pricing.title"
|
||||
@@ -226,7 +235,7 @@ onMounted(async () => {
|
||||
wrapper: 'sm:pl-8'
|
||||
}"
|
||||
>
|
||||
<div aria-hidden="true" class="absolute z-[-1] border-x border-(--ui-border) inset-0 mx-4 sm:mx-6 lg:mx-8" />
|
||||
<div aria-hidden="true" class="absolute z-[-1] border-x border-default inset-0 mx-4 sm:mx-6 lg:mx-8" />
|
||||
<UPricingPlans compact class="-space-x-px">
|
||||
<UPricingPlan
|
||||
v-for="(plan, index) in page.pricing.plans"
|
||||
@@ -246,8 +255,8 @@ onMounted(async () => {
|
||||
>
|
||||
<template #features>
|
||||
<li v-for="(feature, i) in plan.features" :key="i" class="flex items-center gap-2 min-w-0">
|
||||
<UIcon name="i-lucide-circle-check" class="size-5 shrink-0 text-(--ui-primary)" />
|
||||
<MDC :value="feature" unwrap="p" tag="span" class="text-sm truncate text-(--ui-text-accented)" :cache-key="`figma-pricing-plan-${index}-feature-${i}`" />
|
||||
<UIcon name="i-lucide-circle-check" class="size-5 shrink-0 text-primary" />
|
||||
<MDC :value="feature" unwrap="p" tag="span" class="text-sm truncate text-accented" :cache-key="`figma-pricing-plan-${index}-feature-${i}`" />
|
||||
</li>
|
||||
</template>
|
||||
<template #button>
|
||||
@@ -278,7 +287,7 @@ onMounted(async () => {
|
||||
</UPageMarquee>
|
||||
</UPageCTA>
|
||||
<UPageSection v-bind="page.faq" :ui="{ container: 'relative' }">
|
||||
<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-default inset-0 mx-4 sm:mx-6 lg:mx-8" />
|
||||
<UPageAccordion
|
||||
multiple
|
||||
:items="(page.faq.items as any[])"
|
||||
|
||||
@@ -48,7 +48,7 @@ useIntersectionObserver(contributorsRef, ([entry]) => {
|
||||
}"
|
||||
>
|
||||
<template #title>
|
||||
The Intuitive <br> <span class="text-(--ui-primary)">Vue UI Library</span>
|
||||
The Intuitive <br> <span class="text-primary">Vue UI Library</span>
|
||||
</template>
|
||||
<template #description>
|
||||
{{ page.hero.description }}
|
||||
@@ -81,14 +81,14 @@ useIntersectionObserver(contributorsRef, ([entry]) => {
|
||||
pause-on-hover
|
||||
:overlay="false"
|
||||
:ui="{
|
||||
root: '[--gap:--spacing(4)] [--duration:40s] border-(--ui-border) absolute w-full left-0 border-y lg:border-x lg:border-y-0 lg:w-[calc(50%-6px)] 2xl:w-[320px] lg:flex-col',
|
||||
root: '[--gap:--spacing(4)] [--duration:40s] border-default 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]'
|
||||
}"
|
||||
>
|
||||
<ULink
|
||||
v-for="component of components?.slice(0, 10)"
|
||||
:key="component.path"
|
||||
class="relative group/link aspect-video border-(--ui-border) w-[290px] xl:w-[330px] 2xl:w-[320px] 2xl:p-2 2xl:border-y"
|
||||
class="relative group/link aspect-video border-default w-[290px] xl:w-[330px] 2xl:w-[320px] 2xl:p-2 2xl:border-y"
|
||||
:to="component.path"
|
||||
>
|
||||
<UColorModeImage
|
||||
@@ -98,7 +98,7 @@ useIntersectionObserver(contributorsRef, ([entry]) => {
|
||||
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-default 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" />
|
||||
@@ -110,14 +110,14 @@ useIntersectionObserver(contributorsRef, ([entry]) => {
|
||||
reverse
|
||||
:overlay="false"
|
||||
:ui="{
|
||||
root: '[--gap:--spacing(4)] [--duration:40s] border-(--ui-border) absolute w-full mt-[180px] left-0 border-y lg:mt-auto lg:left-auto lg:border-y-0 lg:border-x lg:w-[calc(50%-6px)] 2xl:w-[320px] lg:right-0 lg:flex-col',
|
||||
root: '[--gap:--spacing(4)] [--duration:40s] border-default 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]'
|
||||
}"
|
||||
>
|
||||
<ULink
|
||||
v-for="component of components?.slice(10, 20)"
|
||||
:key="component.path"
|
||||
class="relative group/link aspect-video border-(--ui-border) w-[290px] xl:w-[330px] 2xl:w-[320px] 2xl:p-2 2xl:border-y"
|
||||
class="relative group/link aspect-video border-default w-[290px] xl:w-[330px] 2xl:w-[320px] 2xl:p-2 2xl:border-y"
|
||||
:to="component.path"
|
||||
>
|
||||
<UColorModeImage
|
||||
@@ -127,7 +127,7 @@ useIntersectionObserver(contributorsRef, ([entry]) => {
|
||||
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-default 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" />
|
||||
@@ -168,11 +168,11 @@ useIntersectionObserver(contributorsRef, ([entry]) => {
|
||||
<UIcon :name="feature.icon" class="size-5 shrink-0" />
|
||||
</div>
|
||||
<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-highlighted inline-flex items-center gap-x-1">
|
||||
{{ feature.title }}
|
||||
<UIcon v-if="feature.to" name="i-lucide-arrow-right" class="size-4 shrink-0 opacity-0 group-hover:opacity-100 transition-all duration-200 -translate-x-1 group-hover:translate-x-0" />
|
||||
</h2>
|
||||
<p class="text-sm text-(--ui-text-muted)">
|
||||
<p class="text-sm text-muted">
|
||||
{{ feature.description }}
|
||||
</p>
|
||||
</div>
|
||||
@@ -215,33 +215,33 @@ useIntersectionObserver(contributorsRef, ([entry]) => {
|
||||
:links="page.community.links"
|
||||
orientation="horizontal"
|
||||
:ui="{ features: 'flex items-center gap-4 lg:gap-8' }"
|
||||
class="border-b border-(--ui-border)"
|
||||
class="border-b border-default"
|
||||
>
|
||||
<template #features>
|
||||
<li>
|
||||
<NuxtLink to="https://npm.chart.dev/@nuxt/ui" target="_blank" class="min-w-0">
|
||||
<p class="text-4xl font-semibold text-(--ui-text-highlighted) truncate">
|
||||
<p class="text-4xl font-semibold text-highlighted truncate">
|
||||
{{ format(module?.stats?.downloads ?? 0) }}+
|
||||
</p>
|
||||
<p class="text-(--ui-text-muted) text-sm truncate">monthly downloads</p>
|
||||
<p class="text-muted text-sm truncate">monthly downloads</p>
|
||||
</NuxtLink>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<NuxtLink to="https://github.com/nuxt/ui" target="_blank" class="min-w-0">
|
||||
<p class="text-4xl font-semibold text-(--ui-text-highlighted) truncate">
|
||||
<p class="text-4xl font-semibold text-highlighted truncate">
|
||||
{{ format(module?.stats?.stars ?? 0) }}+
|
||||
</p>
|
||||
<p class="text-(--ui-text-muted) text-sm truncate">GitHub stars</p>
|
||||
<p class="text-muted text-sm truncate">GitHub stars</p>
|
||||
</NuxtLink>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<NuxtLink to="https://github.com/nuxt/ui/graphs/contributors" target="_blank" class="min-w-0">
|
||||
<p class="text-4xl font-semibold text-(--ui-text-highlighted) truncate">
|
||||
175+
|
||||
<p class="text-4xl font-semibold text-highlighted truncate">
|
||||
200+
|
||||
</p>
|
||||
<p class="text-(--ui-text-muted) text-sm truncate">Contributors</p>
|
||||
<p class="text-muted text-sm truncate">Contributors</p>
|
||||
</NuxtLink>
|
||||
</li>
|
||||
</template>
|
||||
@@ -253,10 +253,10 @@ useIntersectionObserver(contributorsRef, ([entry]) => {
|
||||
|
||||
<UPageSection :ui="{ container: 'relative !pb-0 overflow-hidden' }">
|
||||
<template #title>
|
||||
Build faster with Nuxt UI <span class="text-(--ui-primary)">Pro</span>.
|
||||
Build faster with Nuxt UI <span class="text-primary">Pro</span>.
|
||||
</template>
|
||||
<template #description>
|
||||
A collection of premium Vue components, composables and utils built on top of Nuxt UI. <br> Focused on structure and layout, these <span class="text-(--ui-text)">responsive components</span> are designed to be the perfect <span class="text-(--ui-text)">building blocks for your next idea</span>.
|
||||
A collection of premium Vue components, composables and utils built on top of Nuxt UI. <br> Focused on structure and layout, these <span class="text-default">responsive components</span> are designed to be the perfect <span class="text-default">building blocks for your next idea</span>.
|
||||
</template>
|
||||
<template #links>
|
||||
<UButton to="/pro" size="lg">
|
||||
@@ -269,8 +269,8 @@ useIntersectionObserver(contributorsRef, ([entry]) => {
|
||||
|
||||
<LazyStarsBg />
|
||||
|
||||
<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 aria-hidden="true" class="hidden lg:block absolute z-[-1] border-x border-default inset-0 mx-4 sm:mx-6 lg:mx-8" />
|
||||
<div class="relative h-[400px] border border-default bg-muted overflow-hidden border-x-0 -mx-4 sm:-mx-6 lg:mx-0 lg:border-x w-screen lg:w-full">
|
||||
<UPageMarquee reverse orientation="vertical" :overlay="false" :ui="{ root: '[--duration:40s] absolute w-[460px] -left-[100px] -top-[300px] h-[940px] transform-3d rotate-x-55 rotate-y-0 rotate-z-30' }">
|
||||
<img
|
||||
v-for="i in 4"
|
||||
@@ -280,7 +280,7 @@ useIntersectionObserver(contributorsRef, ([entry]) => {
|
||||
height="258"
|
||||
loading="lazy"
|
||||
:alt="`Nuxt UI Pro Screenshot ${i}`"
|
||||
class="aspect-video border border-(--ui-border) rounded-[calc(var(--ui-radius)*2)] bg-white"
|
||||
class="aspect-video border border-default rounded-lg bg-white"
|
||||
>
|
||||
</UPageMarquee>
|
||||
<UPageMarquee orientation="vertical" :overlay="false" :ui="{ root: '[--duration:40s] absolute w-[460px] -top-[400px] left-[480px] h-[1160px] transform-3d rotate-x-55 rotate-y-0 rotate-z-30' }">
|
||||
@@ -292,7 +292,7 @@ useIntersectionObserver(contributorsRef, ([entry]) => {
|
||||
height="258"
|
||||
loading="lazy"
|
||||
:alt="`Nuxt UI Pro Screenshot ${i}`"
|
||||
class="aspect-video border border-(--ui-border) rounded-[calc(var(--ui-radius)*2)] bg-white"
|
||||
class="aspect-video border border-default rounded-lg bg-white"
|
||||
>
|
||||
</UPageMarquee>
|
||||
<UPageMarquee reverse orientation="vertical" :overlay="false" :ui="{ root: 'hidden md:flex [--duration:40s] absolute w-[460px] -top-[300px] left-[1020px] h-[1060px] transform-3d rotate-x-55 rotate-y-0 rotate-z-30' }">
|
||||
@@ -304,7 +304,7 @@ useIntersectionObserver(contributorsRef, ([entry]) => {
|
||||
height="258"
|
||||
loading="lazy"
|
||||
:alt="`Nuxt UI Pro Screenshot ${i}`"
|
||||
class="aspect-video border border-(--ui-border) rounded-[calc(var(--ui-radius)*2)] bg-white"
|
||||
class="aspect-video border border-default rounded-lg bg-white"
|
||||
>
|
||||
</UPageMarquee>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,7 @@ title: Nuxt UI Pro Pricing
|
||||
description: Start for free in development mode, then upgrade to a paid plan to unlock the full features of Nuxt UI Pro when you are ready to launch.
|
||||
pricing:
|
||||
headline: Pricing
|
||||
title: Upgrade to Nuxt UI [Pro]{class="text-(--ui-primary)"}.
|
||||
title: Upgrade to Nuxt UI [Pro]{class="text-primary"}.
|
||||
description: On top of 40+ open source components from Nuxt UI, Pro gives you access to 50+ premium Vue components to create beautiful & responsive Nuxt applications in minutes. It includes all primitives to build landing pages, documentations, blogs, dashboards or entire SaaS products.
|
||||
freePlan:
|
||||
title: Free in development
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
title: Build faster with Nuxt UI Pro.
|
||||
description: A collection of premium Vue components, composables and utils built on top of Nuxt UI, oriented on structure and layout and designed to be used as building blocks for your application.
|
||||
hero:
|
||||
title: Build faster with Nuxt UI [Pro]{class="text-(--ui-primary)"}.
|
||||
description: A collection of premium Vue components, composables and utils built on top of Nuxt UI. :br Focused on structure and layout, these [responsive components]{class="text-(--ui-text)"} are designed to be the perfect [building blocks for your next idea]{class="text-(--ui-text)"}.
|
||||
title: Build faster with Nuxt UI [Pro]{class="text-primary"}.
|
||||
description: A collection of premium Vue components, composables and utils built on top of Nuxt UI. :br Focused on structure and layout, these [responsive components]{class="text-default"} are designed to be the perfect [building blocks for your next idea]{class="text-default"}.
|
||||
links:
|
||||
- label: Buy a license
|
||||
size: xl
|
||||
@@ -62,7 +62,7 @@ testimonial:
|
||||
# avatar:
|
||||
# src: https://github.com/benjamincanac.png
|
||||
mainSection:
|
||||
title: Meet the [Pro Components]{class="text-(--ui-primary)"}.
|
||||
title: Meet the [Pro Components]{class="text-primary"}.
|
||||
description: Code with 50+ components and sections of Nuxt UI Pro to build your next application by reducing the amount of code you need to write.
|
||||
sections:
|
||||
- title: The freedom to build anything
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
title: Official Nuxt UI Pro Templates
|
||||
description: 'Ready to use templates powered by our premium Vue components and Nuxt Content. The templates are responsive, accessible and easy to customize so you can get started in no time.'
|
||||
hero:
|
||||
title: Ship [in minutes]{.text-(--ui-primary)} with :br Nuxt UI Pro Templates
|
||||
title: Ship [in minutes]{.text-primary} with :br Nuxt UI Pro Templates
|
||||
description: 'Ready to use templates powered by our premium Vue components and Nuxt Content.<br class="hidden lg:block"> The templates are responsive, accessible and easy to customize so you can get started in no time.'
|
||||
navigation: false
|
||||
links:
|
||||
@@ -16,8 +16,60 @@ links:
|
||||
variant: outline
|
||||
trailingIcon: i-lucide-arrow-right
|
||||
templates:
|
||||
- title: 'Portfolio'
|
||||
description: "A sleek, modern portfolio template to showcase your work, skills, blog posts, speaking engagements, and provide contact information. Which can customized easily from the `content/` directory."
|
||||
icon: i-lucide-user
|
||||
thumbnail:
|
||||
dark: https://assets.hub.nuxt.com/eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1cmwiOiJodHRwczovL3BvcnRmb2xpby10ZW1wbGF0ZS5udXh0LmRldiIsImlhdCI6MTc0NTkzNDczMX0.XDWnQoyVy3XVtKQD6PLQ8RFUwr4yr1QnVwPxRrjCrro.jpg?theme=dark
|
||||
light: https://assets.hub.nuxt.com/eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1cmwiOiJodHRwczovL3BvcnRmb2xpby10ZW1wbGF0ZS5udXh0LmRldiIsImlhdCI6MTc0NTkzNDczMX0.XDWnQoyVy3XVtKQD6PLQ8RFUwr4yr1QnVwPxRrjCrro.jpg?theme=light
|
||||
features:
|
||||
- title: Sections for Projects, Blog, Speaking & About
|
||||
icon: i-lucide-layout-list
|
||||
- title: Easily editable content via Markdown & YAML
|
||||
icon: i-simple-icons-markdown
|
||||
- title: Fully responsive design
|
||||
icon: i-lucide-smartphone
|
||||
links:
|
||||
- label: Preview
|
||||
to: https://portfolio-template.nuxt.dev
|
||||
target: _blank
|
||||
leadingIcon: i-logos-nuxt-icon
|
||||
trailingIcon: i-lucide-arrow-up-right
|
||||
color: neutral
|
||||
- label: Nuxt Template
|
||||
to: https://github.com/nuxt-ui-pro/portfolio
|
||||
target: _blank
|
||||
icon: i-simple-icons-github
|
||||
color: neutral
|
||||
variant: outline
|
||||
- title: 'Chat'
|
||||
description: "An AI chatbot template designed to help you build your own chatbot with Nuxt UI Pro components and deployed on [NuxtHub](https://hub.nuxt.com)."
|
||||
icon: i-lucide-message-circle
|
||||
thumbnail:
|
||||
dark: https://assets.hub.nuxt.com/eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1cmwiOiJodHRwczovL2NoYXQtdGVtcGxhdGUubnV4dC5kZXYiLCJpYXQiOjE3NDI4NDY2ODB9.n4YCsoNz8xatox7UMoYZFNo7iS1mC_DT0h0A9cKRoTw.jpg?theme=dark
|
||||
light: https://assets.hub.nuxt.com/eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1cmwiOiJodHRwczovL2NoYXQtdGVtcGxhdGUubnV4dC5kZXYiLCJpYXQiOjE3NDI4NDY2ODB9.n4YCsoNz8xatox7UMoYZFNo7iS1mC_DT0h0A9cKRoTw.jpg?theme=light
|
||||
features:
|
||||
- title: Powered by Cloudflare AI models
|
||||
icon: i-simple-icons-cloudflare
|
||||
- title: GitHub OAuth authentication
|
||||
icon: i-lucide-lock
|
||||
- title: Saved chats and messages
|
||||
icon: i-lucide-database
|
||||
links:
|
||||
- label: Preview
|
||||
to: https://chat-template.nuxt.dev
|
||||
target: _blank
|
||||
leadingIcon: i-logos-nuxt-icon
|
||||
trailingIcon: i-lucide-arrow-up-right
|
||||
color: neutral
|
||||
- label: Nuxt Template
|
||||
to: https://github.com/nuxt-ui-pro/chat
|
||||
target: _blank
|
||||
icon: i-simple-icons-github
|
||||
color: neutral
|
||||
variant: outline
|
||||
- title: 'Dashboard'
|
||||
description: "A template to illustrate how to build your own dashboard with the 15+ latest Nuxt UI Pro components, designed specifically to create a consistent look and feel."
|
||||
description: "A template to illustrate how to build your own dashboard with 15+ Nuxt UI Pro components, designed specifically to create a consistent look and feel."
|
||||
icon: i-lucide-bar-chart-big
|
||||
thumbnail:
|
||||
dark: https://assets.hub.nuxt.com/eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1cmwiOiJodHRwczovL2Rhc2hib2FyZC10ZW1wbGF0ZS5udXh0LmRldiIsImlhdCI6MTczOTQ2MzU2N30._VElt4uvLjvAMdnTLytCInOajMElzWDKbmvOaMZhZUI.jpg?theme=dark
|
||||
|
||||
@@ -73,9 +73,9 @@ onMounted(() => {
|
||||
<UPageHero headline="License Activation" :title="title" :description="description" :ui="{ container: 'relative overflow-hidden', wrapper: 'lg:px-12', description: 'text-pretty' }">
|
||||
<LazyStarsBg />
|
||||
|
||||
<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-default 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-default bg-default">
|
||||
<div class="max-w-xl mx-auto">
|
||||
<UForm
|
||||
:schema="schema"
|
||||
|
||||
@@ -35,9 +35,9 @@ useSeoMeta({
|
||||
<LazyStarsBg />
|
||||
|
||||
<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-default inset-0 mx-4 sm:mx-6 lg:mx-8" />
|
||||
</Motion>
|
||||
<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-default bg-muted overflow-hidden border-x-0 -mx-4 sm:-mx-6 lg:mx-0 lg:border-x w-screen lg:w-full">
|
||||
<UPageMarquee reverse orientation="vertical" :overlay="false" :ui="{ root: '[--duration:40s] absolute w-[460px] -left-[100px] -top-[300px] h-[940px] transform-3d rotate-x-55 rotate-y-0 rotate-z-30' }">
|
||||
<img
|
||||
v-for="i in 4"
|
||||
@@ -46,7 +46,7 @@ useSeoMeta({
|
||||
width="460"
|
||||
height="258"
|
||||
:alt="`Nuxt UI Pro Screenshot ${i}`"
|
||||
class="aspect-video border border-(--ui-border) rounded-[calc(var(--ui-radius)*2)] bg-white"
|
||||
class="aspect-video border border-default rounded-lg bg-white"
|
||||
>
|
||||
</UPageMarquee>
|
||||
<UPageMarquee orientation="vertical" :overlay="false" :ui="{ root: '[--duration:40s] absolute w-[460px] -top-[400px] left-[480px] h-[1160px] transform-3d rotate-x-55 rotate-y-0 rotate-z-30' }">
|
||||
@@ -57,7 +57,7 @@ useSeoMeta({
|
||||
width="460"
|
||||
height="258"
|
||||
:alt="`Nuxt UI Pro Screenshot ${i}`"
|
||||
class="aspect-video border border-(--ui-border) rounded-[calc(var(--ui-radius)*2)] bg-white"
|
||||
class="aspect-video border border-default rounded-lg bg-white"
|
||||
>
|
||||
</UPageMarquee>
|
||||
<UPageMarquee reverse orientation="vertical" :overlay="false" :ui="{ root: 'hidden md:flex [--duration:40s] absolute w-[460px] -top-[300px] left-[1020px] h-[1060px] transform-3d rotate-x-55 rotate-y-0 rotate-z-30' }">
|
||||
@@ -68,7 +68,7 @@ useSeoMeta({
|
||||
width="460"
|
||||
height="258"
|
||||
:alt="`Nuxt UI Pro Screenshot ${i}`"
|
||||
class="aspect-video border border-(--ui-border) rounded-[calc(var(--ui-radius)*2)] bg-white"
|
||||
class="aspect-video border border-default rounded-lg bg-white"
|
||||
>
|
||||
</UPageMarquee>
|
||||
</div>
|
||||
@@ -101,10 +101,10 @@ useSeoMeta({
|
||||
container: 'relative',
|
||||
wrapper: 'sm:px-8'
|
||||
}"
|
||||
class="border-t border-(--ui-border)"
|
||||
class="border-t border-default"
|
||||
>
|
||||
<Motion as-child :initial="{ height: 0 }" :while-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-default inset-0 mx-4 sm:mx-6 lg:mx-8" />
|
||||
</Motion>
|
||||
</UPageSection>
|
||||
|
||||
@@ -116,7 +116,7 @@ useSeoMeta({
|
||||
wrapper: 'grid grid-cols-1 lg:grid-cols-2',
|
||||
description: 'lg:mt-0' }"
|
||||
orientation="horizontal"
|
||||
class="rounded-none border-t border-(--ui-border) bg-gradient-to-b from-(--ui-bg-elevated)/50 to-(--ui-bg)"
|
||||
class="rounded-none border-t border-default bg-gradient-to-b from-elevated/50 to-default"
|
||||
>
|
||||
<template #title>
|
||||
<MDC :value="page.mainSection.title" tag="span" unwrap="p" cache-key="pro-main-section-title" />
|
||||
@@ -134,7 +134,7 @@ useSeoMeta({
|
||||
:reverse="section.reverse"
|
||||
:features="section.features"
|
||||
orientation="horizontal"
|
||||
:class="{ 'border-b border-(--ui-border)': index === page.sections.length - 1 }"
|
||||
:class="{ 'border-b border-default': index === page.sections.length - 1 }"
|
||||
:ui="{
|
||||
container: index === 0 ? 'pb-0 sm:pb-0 lg:pb-0 py-16 sm:py-16 lg:py-16' : ''
|
||||
}"
|
||||
@@ -145,10 +145,10 @@ useSeoMeta({
|
||||
<UPageSection
|
||||
id="templates"
|
||||
v-bind="page.templates"
|
||||
class="overflow-hidden border-x border-(--ui-border)"
|
||||
class="overflow-hidden border-x border-default"
|
||||
:ui="{ container: 'relative' }"
|
||||
>
|
||||
<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-default inset-0 mx-4 sm:mx-6 lg:mx-8" />
|
||||
<UCarousel
|
||||
v-slot="{ item }"
|
||||
loop
|
||||
@@ -160,7 +160,7 @@ useSeoMeta({
|
||||
:ui="{
|
||||
item: 'basis-1/2',
|
||||
container: 'py-2',
|
||||
viewport: 'border-x border-(--ui-border)',
|
||||
viewport: 'border-x border-default',
|
||||
arrows: 'hidden 2xl:block'
|
||||
}"
|
||||
>
|
||||
@@ -181,7 +181,7 @@ useSeoMeta({
|
||||
:light="item.thumbnail.light"
|
||||
:dark="item.thumbnail.dark"
|
||||
:alt="item.title"
|
||||
class="rounded-lg w-full border border-(--ui-border) aspect-video"
|
||||
class="rounded-lg w-full border border-default aspect-video"
|
||||
loading="lazy"
|
||||
/>
|
||||
</UPageCard>
|
||||
@@ -199,7 +199,7 @@ useSeoMeta({
|
||||
<LazyStarsBg />
|
||||
|
||||
<video
|
||||
class="rounded-[var(--ui-radius)] z-10"
|
||||
class="rounded-sm z-10"
|
||||
preload="none"
|
||||
poster="https://res.cloudinary.com/nuxt/video/upload/so_3.3/v1708511800/ui-pro/video-nuxt-ui-pro_kwfbdh.jpg"
|
||||
:controls="true"
|
||||
|
||||
@@ -29,13 +29,13 @@ useSeoMeta({
|
||||
|
||||
<LazyStarsBg />
|
||||
|
||||
<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-default 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-default gap-8 lg:gap-0">
|
||||
<UPricingPlan
|
||||
v-bind="page.pricing.freePlan"
|
||||
variant="naked"
|
||||
class="lg:rounded-none border-x border-(--ui-border) border-t border-b lg:border-b-0"
|
||||
class="lg:rounded-none border-x border-default border-t border-b lg:border-b-0"
|
||||
/>
|
||||
<UPricingPlans compact>
|
||||
<UPricingPlan
|
||||
@@ -48,7 +48,7 @@ useSeoMeta({
|
||||
:billing-period="plan.billing_period"
|
||||
:billing-cycle="plan.billing_cycle"
|
||||
:variant="plan.highlight ? 'soft' : 'outline'"
|
||||
:class="['lg:rounded-none', { 'border-2 lg:border lg:border-x-0 border-(--ui-primary) lg:border-(--ui-border)': plan.highlight }]"
|
||||
:class="['lg:rounded-none', { 'border-2 lg:border lg:border-x-0 border-primary lg:border-default': plan.highlight }]"
|
||||
:features="plan.features"
|
||||
:button="plan.button"
|
||||
/>
|
||||
@@ -58,12 +58,12 @@ useSeoMeta({
|
||||
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-default"
|
||||
>
|
||||
<template #features>
|
||||
<li v-for="(feature, index) in page.pricing.figma.features" :key="index" class="flex items-center gap-2 min-w-0">
|
||||
<UIcon name="i-lucide-circle-check" class="size-5 text-(--ui-primary) shrink-0" />
|
||||
<MDC :value="feature" unwrap="p" class="text-sm truncate text-(--ui-text-toned)" :cache-key="`pro-pricing-figma-feature-${index}`" />
|
||||
<UIcon name="i-lucide-circle-check" class="size-5 text-primary shrink-0" />
|
||||
<MDC :value="feature" unwrap="p" class="text-sm truncate text-toned" :cache-key="`pro-pricing-figma-feature-${index}`" />
|
||||
</li>
|
||||
</template>
|
||||
</UPricingPlan>
|
||||
@@ -73,7 +73,7 @@ useSeoMeta({
|
||||
<UPageSection
|
||||
id="testimonials"
|
||||
v-bind="page.testimonials"
|
||||
class="border-y border-(--ui-border)"
|
||||
class="border-y border-default"
|
||||
>
|
||||
<UPageMarquee pause-on-hover :ui="{ root: '[--duration:40s]' }">
|
||||
<img
|
||||
@@ -110,7 +110,7 @@ useSeoMeta({
|
||||
class="scroll-mt-(--ui-header-height)"
|
||||
:ui="{ container: 'relative' }"
|
||||
>
|
||||
<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-default inset-0 mx-4 sm:mx-6 lg:mx-8" />
|
||||
<UPageAccordion
|
||||
multiple
|
||||
:items="(page.faq.items as any[])"
|
||||
|
||||
@@ -20,7 +20,7 @@ useSeoMeta({
|
||||
<UPageHero :links="page.links" :ui="{ container: 'relative' }">
|
||||
<LazyStarsBg />
|
||||
|
||||
<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-default inset-0 mx-4 sm:mx-6 lg:mx-8" />
|
||||
|
||||
<template #title>
|
||||
<MDC :value="page.hero.title" unwrap="p" cache-key="pro-templates-hero-title" />
|
||||
@@ -38,10 +38,10 @@ useSeoMeta({
|
||||
:links="template.links"
|
||||
:features="template.features"
|
||||
orientation="horizontal"
|
||||
class="lg:border-t border-(--ui-border)"
|
||||
class="lg:border-t border-default"
|
||||
:ui="{
|
||||
title: 'lg:text-4xl',
|
||||
wrapper: 'lg:py-16 lg:border-r border-(--ui-border) order-last lg:pr-16',
|
||||
wrapper: 'lg:py-16 lg:border-r border-default order-last lg:pr-16',
|
||||
container: 'lg:py-0',
|
||||
links: 'gap-x-3'
|
||||
}"
|
||||
@@ -50,12 +50,12 @@ useSeoMeta({
|
||||
<MDC :value="template.description" unwrap="p" :cache-key="`pro-templates-${index}-description`" />
|
||||
</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-default h-full flex items-center lg: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 }">
|
||||
<UColorModeImage
|
||||
v-if="template.thumbnail"
|
||||
v-bind="template.thumbnail"
|
||||
class="w-full h-auto border lg:border-y lg:border-x-0 border-(--ui-border) rounded-(--ui-radius) lg:rounded-none"
|
||||
class="w-full h-auto border lg:border-y lg:border-x-0 border-default rounded-sm lg:rounded-none"
|
||||
:alt="`Template ${index} thumbnail`"
|
||||
width="656"
|
||||
height="369"
|
||||
|
||||
@@ -29,19 +29,19 @@ defineOgImageComponent('Docs', {
|
||||
}"
|
||||
>
|
||||
<template #top>
|
||||
<div class="absolute z-[-1] rounded-full bg-(--ui-primary) blur-[300px] size-60 sm:size-80 transform -translate-x-1/2 left-1/2 -translate-y-80" />
|
||||
<div class="absolute z-[-1] rounded-full bg-primary blur-[300px] size-60 sm:size-80 transform -translate-x-1/2 left-1/2 -translate-y-80" />
|
||||
</template>
|
||||
|
||||
<LazyStarsBg />
|
||||
|
||||
<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-default inset-0 mx-4 sm:mx-6 lg:mx-8" />
|
||||
|
||||
<div class="border-l border-t border-(--ui-border)">
|
||||
<ul class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 items-start justify-center divide-y divide-x divide-(--ui-border)">
|
||||
<div class="border-l border-t border-default">
|
||||
<ul class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 items-start justify-center divide-y divide-x divide-default">
|
||||
<li
|
||||
v-for="item in page.items"
|
||||
:key="item.name"
|
||||
class="group relative flex items-center justify-center flex-1 size-full p-2 last:border-r last:border-b border-(--ui-border) overflow-hidden"
|
||||
class="group relative flex items-center justify-center flex-1 size-full p-2 last:border-r last:border-b border-default overflow-hidden"
|
||||
>
|
||||
<NuxtLink class="inset-0 absolute" :to="item.url" target="_blank">
|
||||
<span class="sr-only">Go to {{ item.name }}</span>
|
||||
@@ -67,17 +67,6 @@ defineOgImageComponent('Docs', {
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-center -mb-[36px]">
|
||||
<UButton
|
||||
label="Submit your project"
|
||||
trailing-icon="i-lucide-plus"
|
||||
color="neutral"
|
||||
size="lg"
|
||||
to="https://github.com/nuxt/ui/edit/v3/docs/content/showcase.yml"
|
||||
target="_blank"
|
||||
/>
|
||||
</div>
|
||||
</UPageHero>
|
||||
</UMain>
|
||||
</template>
|
||||
|
||||
@@ -41,24 +41,25 @@ const icons = {
|
||||
:ui="{ title: 'text-balance', container: 'relative' }"
|
||||
>
|
||||
<template #top>
|
||||
<div class="absolute z-[-1] rounded-full bg-(--ui-primary) blur-[300px] size-60 sm:size-80 transform -translate-x-1/2 left-1/2 -translate-y-80" />
|
||||
<div class="absolute z-[-1] rounded-full bg-primary blur-[300px] size-60 sm:size-80 transform -translate-x-1/2 left-1/2 -translate-y-80" />
|
||||
</template>
|
||||
|
||||
<LazyStarsBg />
|
||||
</UPageHero>
|
||||
|
||||
<UPageSection :ui="{ container: '!pt-0' }">
|
||||
<UPageGrid class="xl:grid-cols-4">
|
||||
<UPageGrid class="xl:grid-cols-5">
|
||||
<UPageCard
|
||||
v-for="(user, index) in module?.team"
|
||||
:key="index"
|
||||
:title="user.name"
|
||||
:description="[user.pronouns, user.location].filter(Boolean).join(' ・ ')"
|
||||
:ui="{
|
||||
wrapper: 'items-center',
|
||||
container: 'gap-y-4 lg:p-8',
|
||||
leading: 'flex justify-center',
|
||||
title: 'text-center',
|
||||
description: 'text-center text-(--ui-text-muted)'
|
||||
description: 'text-center text-muted'
|
||||
}"
|
||||
variant="subtle"
|
||||
>
|
||||
@@ -123,10 +124,11 @@ const icons = {
|
||||
:key="contributor.username"
|
||||
:title="contributor.username"
|
||||
:ui="{
|
||||
wrapper: 'items-center',
|
||||
container: 'gap-y-2',
|
||||
leading: 'flex justify-center',
|
||||
title: 'text-center',
|
||||
description: 'text-center text-(--ui-text-muted)'
|
||||
description: 'text-center text-muted'
|
||||
}"
|
||||
>
|
||||
<template #leading>
|
||||
|
||||
@@ -4,7 +4,7 @@ description: 'Nuxt UI harnesses the combined strengths of Reka UI, Tailwind CSS,
|
||||
navigation.icon: i-lucide-house
|
||||
---
|
||||
|
||||
<iframe width="100%" height="100%" src="https://www.youtube-nocookie.com/embed/_eQxomah-nA?si=pDSzchUBDKb2NQu7" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen style="aspect-ratio: 16/9;" class="rounded-[calc(var(--ui-radius)*1.5)]"></iframe>
|
||||
<iframe width="100%" height="100%" src="https://www.youtube-nocookie.com/embed/_eQxomah-nA?si=pDSzchUBDKb2NQu7" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen style="aspect-ratio: 16/9;" class="rounded-md"></iframe>
|
||||
|
||||
## Reka UI
|
||||
|
||||
|
||||
@@ -76,16 +76,18 @@ export default defineNuxtConfig({
|
||||
It's recommended to install the [Tailwind CSS IntelliSense](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss) extension for VSCode and add the following settings:
|
||||
|
||||
```json [.vscode/settings.json]
|
||||
"files.associations": {
|
||||
"*.css": "tailwindcss"
|
||||
},
|
||||
"editor.quickSuggestions": {
|
||||
"strings": "on"
|
||||
},
|
||||
"tailwindCSS.classAttributes": ["class", "ui"],
|
||||
"tailwindCSS.experimental.classRegex": [
|
||||
["ui:\\s*{([^)]*)\\s*}", "(?:'|\"|`)([^']*)(?:'|\"|`)"]
|
||||
]
|
||||
{
|
||||
"files.associations": {
|
||||
"*.css": "tailwindcss"
|
||||
},
|
||||
"editor.quickSuggestions": {
|
||||
"strings": "on"
|
||||
},
|
||||
"tailwindCSS.classAttributes": ["class", "ui"],
|
||||
"tailwindCSS.experimental.classRegex": [
|
||||
["ui:\\s*{([^)]*)\\s*}", "(?:'|\"|`)([^']*)(?:'|\"|`)"]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
::
|
||||
|
||||
@@ -99,6 +99,10 @@ app.use(ui)
|
||||
app.mount('#app')
|
||||
```
|
||||
|
||||
::note{to="#inertia"}
|
||||
If you're using [Inertia.js](https://inertiajs.com/), you can skip the `vue-router` setup as Inertia provides its own routing system.
|
||||
::
|
||||
|
||||
#### Import Tailwind CSS and Nuxt UI in your CSS
|
||||
|
||||
```css [assets/main.css]
|
||||
@@ -136,16 +140,18 @@ app.mount('#app')
|
||||
It's recommended to install the [Tailwind CSS IntelliSense](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss) extension for VSCode and add the following settings:
|
||||
|
||||
```json [.vscode/settings.json]
|
||||
"files.associations": {
|
||||
"*.css": "tailwindcss"
|
||||
},
|
||||
"editor.quickSuggestions": {
|
||||
"strings": "on"
|
||||
},
|
||||
"tailwindCSS.classAttributes": ["class", "ui"],
|
||||
"tailwindCSS.experimental.classRegex": [
|
||||
["ui:\\s*{([^)]*)\\s*}", "(?:'|\"|`)([^']*)(?:'|\"|`)"]
|
||||
]
|
||||
{
|
||||
"files.associations": {
|
||||
"*.css": "tailwindcss"
|
||||
},
|
||||
"editor.quickSuggestions": {
|
||||
"strings": "on"
|
||||
},
|
||||
"tailwindCSS.classAttributes": ["class", "ui"],
|
||||
"tailwindCSS.experimental.classRegex": [
|
||||
["ui:\\s*{([^)]*)\\s*}", "(?:'|\"|`)([^']*)(?:'|\"|`)"]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
::
|
||||
@@ -311,6 +317,29 @@ export default defineConfig({
|
||||
This option adds the `transition-colors` class on components with hover or active states.
|
||||
::
|
||||
|
||||
### `inertia`
|
||||
|
||||
Use the `inertia` option to enable compatibility with [Inertia.js](https://inertiajs.com/).
|
||||
|
||||
```ts [vite.config.ts]
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import ui from '@nuxt/ui/vite'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
vue(),
|
||||
ui({
|
||||
inertia: true
|
||||
})
|
||||
]
|
||||
})
|
||||
```
|
||||
|
||||
::note
|
||||
When using this option, `vue-router` is not required as Inertia.js provides its own routing system. The components that would normally use `RouterLink` will automatically use Inertia's `InertiaLink` component instead.
|
||||
::
|
||||
|
||||
## Continuous Releases
|
||||
|
||||
Nuxt UI uses [pkg.pr.new](https://github.com/stackblitz-labs/pkg.pr.new) for continuous preview releases, providing developers with instant access to the latest features and bug fixes without waiting for official releases.
|
||||
|
||||
@@ -195,23 +195,14 @@ You can also use the new [design tokens](/getting-started/theme#neutral-palette)
|
||||
```diff
|
||||
<template>
|
||||
- <p class="text-gray-500 dark:text-gray-400" />
|
||||
+ <p class="text-(--ui-text-muted)" />
|
||||
+ <p class="text-muted" />
|
||||
|
||||
- <p class="text-gray-900 dark:text-white" />
|
||||
+ <p class="text-(--ui-text-highlighted)" />
|
||||
+ <p class="text-highlighted" />
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
- The `DEFAULT` shade that let you write `text-primary` no longer exists, you can use [color shades](/getting-started/theme#color-shades) instead:
|
||||
|
||||
```diff
|
||||
<template>
|
||||
- <p class="text-primary">Hello</p>
|
||||
+ <p class="text-(--ui-primary)">Hello</p>
|
||||
</template>
|
||||
```
|
||||
|
||||
- The `gray`, `black` and `white` in the `color` props have been removed in favor of `neutral`:
|
||||
|
||||
```diff
|
||||
@@ -513,7 +504,7 @@ const count = ref(0)
|
||||
</script>
|
||||
```
|
||||
|
||||
Closing a modal is now done through the `close` event. The `modal.open` method now returns a promise that resolves to the result of the modal whenever the modal is close:
|
||||
Closing a modal is now done through the `close` event. The `modal.open` method now returns an instance that can be used to await for the result of the modal whenever the modal is closed:
|
||||
|
||||
```diff
|
||||
<script setup lang="ts">
|
||||
@@ -532,10 +523,12 @@ import { ModalExampleComponent } from '#components'
|
||||
- })
|
||||
- }
|
||||
+ async function openModal() {
|
||||
+ const result = await modal.open(ModalExampleComponent, {
|
||||
+ const instance = modal.open(ModalExampleComponent, {
|
||||
+ count: count.value
|
||||
+ })
|
||||
+
|
||||
+ const result = await instance.result
|
||||
+
|
||||
+ if (result) {
|
||||
+ toast.add({ title: 'Success!' })
|
||||
+ }
|
||||
|
||||
@@ -118,17 +118,7 @@ Learn more about automatic content detection in the detecting classes in source
|
||||
|
||||
## Design system
|
||||
|
||||
Nuxt UI extends Tailwind CSS's theming capabilities, providing a flexible design system with pre-configured color aliases and CSS variables. This allows for easy customization and quick adaptation of the UI to your brand's aesthetic.
|
||||
|
||||
### Colors
|
||||
|
||||
::framework-only
|
||||
#nuxt
|
||||
Nuxt UI leverages Nuxt [App Config](https://nuxt.com/docs/guide/directory-structure/app-config#app-config-file) to provide customizable color aliases based on [Tailwind CSS colors](https://tailwindcss.com/docs/customizing-colors#color-palette-reference):
|
||||
|
||||
#vue
|
||||
Nuxt UI leverages Vite config to provide customizable color aliases based on [Tailwind CSS colors](https://tailwindcss.com/docs/customizing-colors#color-palette-reference):
|
||||
::
|
||||
Nuxt UI extends Tailwind CSS's theming capabilities, providing a flexible design system with pre-configured color aliases based on [Tailwind CSS colors](https://tailwindcss.com/docs/customizing-colors#color-palette-reference). This allows for easy customization and quick adaptation of the UI to your brand's aesthetic.
|
||||
|
||||
| Color | Default | Description |
|
||||
| --- | --- | --- |
|
||||
@@ -140,10 +130,27 @@ Nuxt UI leverages Vite config to provide customizable color aliases based on [Ta
|
||||
| `error`{color="error"} | `red` | Used for form error validation states. |
|
||||
| `neutral` | `slate` | Neutral color for backgrounds, text, etc. |
|
||||
|
||||
These colors are used to style the components but also to generate the `color` props:
|
||||
|
||||
::component-code{slug="button"}
|
||||
---
|
||||
props:
|
||||
color: primary
|
||||
slots:
|
||||
default: Button
|
||||
---
|
||||
::
|
||||
|
||||
::note
|
||||
Try the :prose-icon{name="i-lucide-swatch-book" class="text-primary"} theme picker in the header above to change `primary` and `neutral` colors.
|
||||
::
|
||||
|
||||
### Configuration
|
||||
|
||||
::framework-only
|
||||
#nuxt
|
||||
:::div
|
||||
You can configure these color aliases at runtime in your `app.config.ts` file under the `ui.colors` key, allowing for dynamic theme customization without requiring an application rebuild:
|
||||
You can configure these color aliases at runtime in your [`app.config.ts`](https://nuxt.com/docs/guide/directory-structure/app-config#app-config-file) file under the `ui.colors` key, allowing for dynamic theme customization without requiring an application rebuild:
|
||||
|
||||
```ts [app.config.ts]
|
||||
export default defineAppConfig({
|
||||
@@ -222,27 +229,18 @@ export default defineConfig({
|
||||
|
||||
::
|
||||
|
||||
::note
|
||||
Try the :prose-icon{name="i-lucide-swatch-book" class="text-(--ui-primary)"} theme picker in the header above to change `primary` and `neutral` colors.
|
||||
::caution
|
||||
When configuring your theme colors, you must use either color names from the [default Tailwind palette](https://tailwindcss.com/docs/colors) (like 'blue', 'green', etc.) or reference custom colors that you've previously defined in your [CSS file](#theme).
|
||||
::
|
||||
|
||||
These colors are used to style the components but also to generate the `color` variants:
|
||||
|
||||
::component-code{slug="button"}
|
||||
---
|
||||
props:
|
||||
color: primary
|
||||
slots:
|
||||
default: Button
|
||||
---
|
||||
::
|
||||
### Extend colors
|
||||
|
||||
::framework-only
|
||||
#nuxt
|
||||
:::tip
|
||||
You can add you own dynamic color aliases in your `app.config.ts`, you just have to make sure to define them in the [`ui.theme.colors`](/getting-started/installation/nuxt#themecolors) option in your `nuxt.config.ts` file.
|
||||
:::div
|
||||
You can add you own dynamic color aliases in your `app.config.ts`, you just have to make sure to define them in the [`ui.theme.colors`](/getting-started/installation/nuxt#themecolors) option in your `nuxt.config.ts` file:
|
||||
|
||||
```ts [app.config.ts]
|
||||
```ts [app.config.ts]{4}
|
||||
export default defineAppConfig({
|
||||
ui: {
|
||||
colors: {
|
||||
@@ -252,11 +250,19 @@ export default defineAppConfig({
|
||||
})
|
||||
```
|
||||
|
||||
```ts [nuxt.config.ts]
|
||||
```ts [nuxt.config.ts]{7}
|
||||
export default defineNuxtConfig({
|
||||
ui: {
|
||||
theme: {
|
||||
colors: ['primary', 'secondary', 'tertiary', 'info', 'success', 'warning', 'error']
|
||||
colors: [
|
||||
'primary',
|
||||
'secondary',
|
||||
'tertiary',
|
||||
'info',
|
||||
'success',
|
||||
'warning',
|
||||
'error'
|
||||
]
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -266,9 +272,9 @@ export default defineNuxtConfig({
|
||||
|
||||
#vue
|
||||
|
||||
:::tip
|
||||
:::div
|
||||
|
||||
You can add you own dynamic color aliases in your `vite.config.ts`, you just have to make sure to also define them in the [`theme.colors`](/getting-started/installation/vue#themecolors) option of the `ui` plugin.
|
||||
You can add you own dynamic color aliases in your `vite.config.ts`, you just have to make sure to also define them in the [`theme.colors`](/getting-started/installation/vue#themecolors) option of the `ui` plugin:
|
||||
|
||||
::::module-only
|
||||
|
||||
@@ -276,7 +282,7 @@ You can add you own dynamic color aliases in your `vite.config.ts`, you just hav
|
||||
|
||||
:::::div
|
||||
|
||||
```ts [vite.config.ts]
|
||||
```ts [vite.config.ts]{11,18}
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import ui from '@nuxt/ui/vite'
|
||||
@@ -291,7 +297,15 @@ export default defineConfig({
|
||||
}
|
||||
},
|
||||
theme: {
|
||||
colors: ['primary', 'secondary', 'tertiary', 'info', 'success', 'warning', 'error']
|
||||
colors: [
|
||||
'primary',
|
||||
'secondary',
|
||||
'tertiary',
|
||||
'info',
|
||||
'success',
|
||||
'warning',
|
||||
'error'
|
||||
]
|
||||
}
|
||||
})
|
||||
]
|
||||
@@ -304,7 +318,7 @@ export default defineConfig({
|
||||
|
||||
:::::div
|
||||
|
||||
```ts [vite.config.ts]
|
||||
```ts [vite.config.ts]{11,18}
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import uiPro from '@nuxt/ui-pro/vite'
|
||||
@@ -316,10 +330,18 @@ export default defineConfig({
|
||||
ui: {
|
||||
colors: {
|
||||
tertiary: 'indigo'
|
||||
},
|
||||
}
|
||||
},
|
||||
theme: {
|
||||
colors: ['primary', 'secondary', 'tertiary', 'info', 'success', 'warning', 'error']
|
||||
colors: [
|
||||
'primary',
|
||||
'secondary',
|
||||
'tertiary',
|
||||
'info',
|
||||
'success',
|
||||
'warning',
|
||||
'error'
|
||||
]
|
||||
}
|
||||
})
|
||||
]
|
||||
@@ -334,13 +356,13 @@ export default defineConfig({
|
||||
|
||||
::
|
||||
|
||||
### Tokens
|
||||
## CSS Variables
|
||||
|
||||
Nuxt UI leverages a robust system of CSS variables as design tokens to ensure consistent and flexible component styling. These tokens form the foundation of the theming system, offering smooth support for both light and dark modes.
|
||||
|
||||
#### Color Shades
|
||||
### Colors
|
||||
|
||||
Nuxt UI automatically creates a CSS variable for each color alias you define which represent the default shade used in both light and dark modes:
|
||||
Nuxt UI provides a CSS variable for each color alias you define which represent the default shade used in both light and dark modes:
|
||||
|
||||
::code-group
|
||||
|
||||
@@ -368,12 +390,125 @@ Nuxt UI automatically creates a CSS variable for each color alias you define whi
|
||||
|
||||
::
|
||||
|
||||
::note
|
||||
You can use these variables in classes like `text-(--ui-primary)`, it will automatically adapt to the current color scheme.
|
||||
These CSS variables are defined in Tailwind CSS's `@theme` so you can use them as classes:
|
||||
|
||||
::code-preview
|
||||
[Primary]{class="text-primary text-sm px-4"}
|
||||
[Secondary]{class="text-secondary text-sm px-4"}
|
||||
[Success]{class="text-success text-sm px-4"}
|
||||
[Info]{class="text-info text-sm px-4"}
|
||||
[Warning]{class="text-warning text-sm px-4"}
|
||||
[Error]{class="text-error text-sm px-4"}
|
||||
|
||||
#code
|
||||
```vue
|
||||
<template>
|
||||
<span class="text-primary">Primary</span>
|
||||
<span class="text-secondary">Secondary</span>
|
||||
<span class="text-success">Success</span>
|
||||
<span class="text-info">Info</span>
|
||||
<span class="text-warning">Warning</span>
|
||||
<span class="text-error">Error</span>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
::tip
|
||||
You can change which shade is used for each color on light and dark mode:
|
||||
::note
|
||||
This is how the `@theme` is generated for each color alias:
|
||||
|
||||
:::code-collapse{class="[&>div]:!my-0"}
|
||||
```scss
|
||||
@theme default {
|
||||
--color-primary: var(--ui-primary);
|
||||
--color-primary-50: var(--ui-color-primary-50);
|
||||
--color-primary-100: var(--ui-color-primary-100);
|
||||
--color-primary-200: var(--ui-color-primary-200);
|
||||
--color-primary-300: var(--ui-color-primary-300);
|
||||
--color-primary-400: var(--ui-color-primary-400);
|
||||
--color-primary-500: var(--ui-color-primary-500);
|
||||
--color-primary-600: var(--ui-color-primary-600);
|
||||
--color-primary-700: var(--ui-color-primary-700);
|
||||
--color-primary-800: var(--ui-color-primary-800);
|
||||
--color-primary-900: var(--ui-color-primary-900);
|
||||
--color-primary-950: var(--ui-color-primary-950);
|
||||
--color-secondary: var(--ui-secondary);
|
||||
--color-secondary-50: var(--ui-color-secondary-50);
|
||||
--color-secondary-100: var(--ui-color-secondary-100);
|
||||
--color-secondary-200: var(--ui-color-secondary-200);
|
||||
--color-secondary-300: var(--ui-color-secondary-300);
|
||||
--color-secondary-400: var(--ui-color-secondary-400);
|
||||
--color-secondary-500: var(--ui-color-secondary-500);
|
||||
--color-secondary-600: var(--ui-color-secondary-600);
|
||||
--color-secondary-700: var(--ui-color-secondary-700);
|
||||
--color-secondary-800: var(--ui-color-secondary-800);
|
||||
--color-secondary-900: var(--ui-color-secondary-900);
|
||||
--color-secondary-950: var(--ui-color-secondary-950);
|
||||
--color-success: var(--ui-success);
|
||||
--color-success-50: var(--ui-color-success-50);
|
||||
--color-success-100: var(--ui-color-success-100);
|
||||
--color-success-200: var(--ui-color-success-200);
|
||||
--color-success-300: var(--ui-color-success-300);
|
||||
--color-success-400: var(--ui-color-success-400);
|
||||
--color-success-500: var(--ui-color-success-500);
|
||||
--color-success-600: var(--ui-color-success-600);
|
||||
--color-success-700: var(--ui-color-success-700);
|
||||
--color-success-800: var(--ui-color-success-800);
|
||||
--color-success-900: var(--ui-color-success-900);
|
||||
--color-success-950: var(--ui-color-success-950);
|
||||
--color-info: var(--ui-info);
|
||||
--color-info-50: var(--ui-color-info-50);
|
||||
--color-info-100: var(--ui-color-info-100);
|
||||
--color-info-200: var(--ui-color-info-200);
|
||||
--color-info-300: var(--ui-color-info-300);
|
||||
--color-info-400: var(--ui-color-info-400);
|
||||
--color-info-500: var(--ui-color-info-500);
|
||||
--color-info-600: var(--ui-color-info-600);
|
||||
--color-info-700: var(--ui-color-info-700);
|
||||
--color-info-800: var(--ui-color-info-800);
|
||||
--color-info-900: var(--ui-color-info-900);
|
||||
--color-info-950: var(--ui-color-info-950);
|
||||
--color-warning: var(--ui-warning);
|
||||
--color-warning-50: var(--ui-color-warning-50);
|
||||
--color-warning-100: var(--ui-color-warning-100);
|
||||
--color-warning-200: var(--ui-color-warning-200);
|
||||
--color-warning-300: var(--ui-color-warning-300);
|
||||
--color-warning-400: var(--ui-color-warning-400);
|
||||
--color-warning-500: var(--ui-color-warning-500);
|
||||
--color-warning-600: var(--ui-color-warning-600);
|
||||
--color-warning-700: var(--ui-color-warning-700);
|
||||
--color-warning-800: var(--ui-color-warning-800);
|
||||
--color-warning-900: var(--ui-color-warning-900);
|
||||
--color-warning-950: var(--ui-color-warning-950);
|
||||
--color-error: var(--ui-error);
|
||||
--color-error-50: var(--ui-color-error-50);
|
||||
--color-error-100: var(--ui-color-error-100);
|
||||
--color-error-200: var(--ui-color-error-200);
|
||||
--color-error-300: var(--ui-color-error-300);
|
||||
--color-error-400: var(--ui-color-error-400);
|
||||
--color-error-500: var(--ui-color-error-500);
|
||||
--color-error-600: var(--ui-color-error-600);
|
||||
--color-error-700: var(--ui-color-error-700);
|
||||
--color-error-800: var(--ui-color-error-800);
|
||||
--color-error-900: var(--ui-color-error-900);
|
||||
--color-error-950: var(--ui-color-error-950);
|
||||
--color-neutral-50: var(--ui-color-neutral-50);
|
||||
--color-neutral-100: var(--ui-color-neutral-100);
|
||||
--color-neutral-200: var(--ui-color-neutral-200);
|
||||
--color-neutral-300: var(--ui-color-neutral-300);
|
||||
--color-neutral-400: var(--ui-color-neutral-400);
|
||||
--color-neutral-500: var(--ui-color-neutral-500);
|
||||
--color-neutral-600: var(--ui-color-neutral-600);
|
||||
--color-neutral-700: var(--ui-color-neutral-700);
|
||||
--color-neutral-800: var(--ui-color-neutral-800);
|
||||
--color-neutral-900: var(--ui-color-neutral-900);
|
||||
--color-neutral-950: var(--ui-color-neutral-950);
|
||||
}
|
||||
```
|
||||
:::
|
||||
|
||||
::
|
||||
|
||||
You can change which shade is used for each color on light and dark mode in your `main.css` file:
|
||||
|
||||
::module-only
|
||||
#ui
|
||||
@@ -413,10 +548,6 @@ You can change which shade is used for each color on light and dark mode:
|
||||
:::
|
||||
::
|
||||
|
||||
::
|
||||
|
||||
#### Black as Primary Color
|
||||
|
||||
::framework-only
|
||||
#nuxt
|
||||
:::p
|
||||
@@ -467,7 +598,7 @@ You cannot set `primary: 'black'`{lang="ts-type"} in your [`vite.config.ts`](#co
|
||||
:::
|
||||
::
|
||||
|
||||
#### Neutral Palette
|
||||
### Neutral
|
||||
|
||||
Nuxt UI provides a comprehensive set of design tokens for the `neutral` color palette, ensuring consistent and accessible UI styling across both light and dark modes. These tokens offer fine-grained control over text, background, and border colors:
|
||||
|
||||
@@ -475,150 +606,269 @@ Nuxt UI provides a comprehensive set of design tokens for the `neutral` color pa
|
||||
|
||||
```css [Light]
|
||||
:root {
|
||||
/* Least prominent text */
|
||||
--ui-text-dimmed: var(--ui-color-neutral-400);
|
||||
/* Slightly muted text */
|
||||
--ui-text-muted: var(--ui-color-neutral-500);
|
||||
/* Moderately prominent text */
|
||||
--ui-text-toned: var(--ui-color-neutral-600);
|
||||
/* Default text color */
|
||||
--ui-text: var(--ui-color-neutral-700);
|
||||
/* Most prominent text */
|
||||
--ui-text-highlighted: var(--ui-color-neutral-900);
|
||||
--ui-text-inverted: var(--color-white);
|
||||
|
||||
/* Main background color */
|
||||
--ui-bg: var(--color-white);
|
||||
/* Subtle background */
|
||||
--ui-bg-muted: var(--ui-color-neutral-50);
|
||||
/* Slightly elevated background */
|
||||
--ui-bg-elevated: var(--ui-color-neutral-100);
|
||||
/* More prominent background */
|
||||
--ui-bg-accented: var(--ui-color-neutral-200);
|
||||
/* Inverted background color */
|
||||
--ui-bg-inverted: var(--ui-color-neutral-900);
|
||||
|
||||
/* Default border color */
|
||||
--ui-border: var(--ui-color-neutral-200);
|
||||
/* Subtle border */
|
||||
--ui-border-muted: var(--ui-color-neutral-200);
|
||||
/* More prominent border */
|
||||
--ui-border-accented: var(--ui-color-neutral-300);
|
||||
/* Inverted border color */
|
||||
--ui-border-inverted: var(--ui-color-neutral-900);
|
||||
}
|
||||
```
|
||||
|
||||
```css [Dark]
|
||||
.dark {
|
||||
/* Least prominent text */
|
||||
--ui-text-dimmed: var(--ui-color-neutral-500);
|
||||
/* Slightly muted text */
|
||||
--ui-text-muted: var(--ui-color-neutral-400);
|
||||
/* Moderately prominent text */
|
||||
--ui-text-toned: var(--ui-color-neutral-300);
|
||||
/* Default text color */
|
||||
--ui-text: var(--ui-color-neutral-200);
|
||||
/* Most prominent text */
|
||||
--ui-text-highlighted: var(--color-white);
|
||||
--ui-text-inverted: var(--ui-color-neutral-900);
|
||||
|
||||
/* Main background color */
|
||||
--ui-bg: var(--ui-color-neutral-900);
|
||||
/* Subtle background */
|
||||
--ui-bg-muted: var(--ui-color-neutral-800);
|
||||
/* Slightly elevated background */
|
||||
--ui-bg-elevated: var(--ui-color-neutral-800);
|
||||
/* More prominent background */
|
||||
--ui-bg-accented: var(--ui-color-neutral-700);
|
||||
/* Inverted background color */
|
||||
--ui-bg-inverted: var(--color-white);
|
||||
|
||||
/* Default border color */
|
||||
--ui-border: var(--ui-color-neutral-800);
|
||||
/* Subtle border */
|
||||
--ui-border-muted: var(--ui-color-neutral-700);
|
||||
/* More prominent border */
|
||||
--ui-border-accented: var(--ui-color-neutral-700);
|
||||
/* Inverted border color */
|
||||
--ui-border-inverted: var(--color-white);
|
||||
}
|
||||
```
|
||||
|
||||
::
|
||||
|
||||
These CSS variables are defined in Tailwind CSS's `@theme` so you can use them as classes:
|
||||
|
||||
::code-preview
|
||||
[Dimmed]{class="text-dimmed text-sm px-4 py-1.5 inline-block rounded-md"}
|
||||
[Muted]{class="text-muted text-sm px-4 py-1.5 inline-block rounded-md"}
|
||||
[Toned]{class="text-toned text-sm px-4 py-1.5 inline-block rounded-md"}
|
||||
[Text]{class="text-default text-sm px-4 py-1.5 inline-block rounded-md"}
|
||||
[Highlighted]{class="text-highlighted text-sm px-4 py-1.5 inline-block rounded-md"}
|
||||
[Inverted]{class="text-inverted bg-inverted text-sm px-4 py-1.5 inline-block rounded-md"}
|
||||
|
||||
#code
|
||||
```vue
|
||||
<template>
|
||||
<span class="text-dimmed">Dimmed</span>
|
||||
<span class="text-muted">Muted</span>
|
||||
<span class="text-toned">Toned</span>
|
||||
<span class="text-default">Text</span>
|
||||
<span class="text-highlighted">Highlighted</span>
|
||||
<span class="text-inverted bg-inverted">Inverted</span>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
::code-preview
|
||||
[Default]{class="bg-default text-sm px-4 py-1.5 inline-block rounded-md mr-2"}
|
||||
[Muted]{class="bg-muted text-sm px-4 py-1.5 inline-block rounded-md mr-2"}
|
||||
[Elevated]{class="bg-elevated text-sm px-4 py-1.5 inline-block rounded-md mr-2"}
|
||||
[Accented]{class="bg-accented text-sm px-4 py-1.5 inline-block rounded-md mr-2"}
|
||||
[Inverted]{class="bg-inverted text-inverted text-sm px-4 py-1.5 inline-block rounded-md"}
|
||||
|
||||
#code
|
||||
```vue
|
||||
<template>
|
||||
<div class="bg-default">Default</div>
|
||||
<div class="bg-muted">Muted</div>
|
||||
<div class="bg-elevated">Elevated</div>
|
||||
<div class="bg-accented">Accented</div>
|
||||
<div class="bg-inverted text-inverted">Inverted</div>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
::code-preview
|
||||
[Default]{class="border-2 border-default text-sm px-4 py-1.5 inline-block rounded-md mr-2"}
|
||||
[Muted]{class="border-2 border-muted text-sm px-4 py-1.5 inline-block rounded-md mr-2"}
|
||||
[Accented]{class="border-2 border-accented text-sm px-4 py-1.5 inline-block rounded-md mr-2"}
|
||||
[Inverted]{class="border-2 border-inverted text-sm px-4 py-1.5 inline-block rounded-md"}
|
||||
|
||||
#code
|
||||
```vue
|
||||
<template>
|
||||
<div class="border border-default">Default</div>
|
||||
<div class="border border-muted">Muted</div>
|
||||
<div class="border border-accented">Accented</div>
|
||||
<div class="border border-inverted">Inverted</div>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
::note
|
||||
Nuxt UI automatically applies a text and background color on the `<body>` element of your app:
|
||||
This is how the `@theme` is generated for each design token:
|
||||
|
||||
:::code-collapse{class="[&>div]:!my-0"}
|
||||
```scss
|
||||
@theme default {
|
||||
--text-color-dimmed: var(--ui-text-dimmed);
|
||||
--text-color-muted: var(--ui-text-muted);
|
||||
--text-color-toned: var(--ui-text-toned);
|
||||
--text-color-default: var(--ui-text);
|
||||
--text-color-highlighted: var(--ui-text-highlighted);
|
||||
--text-color-inverted: var(--ui-text-inverted);
|
||||
--background-color-default: var(--ui-bg);
|
||||
--background-color-muted: var(--ui-bg-muted);
|
||||
--background-color-elevated: var(--ui-bg-elevated);
|
||||
--background-color-accented: var(--ui-bg-accented);
|
||||
--background-color-inverted: var(--ui-bg-inverted);
|
||||
--background-color-border: var(--ui-border);
|
||||
--border-color-default: var(--ui-border);
|
||||
--border-color-muted: var(--ui-border-muted);
|
||||
--border-color-accented: var(--ui-border-accented);
|
||||
--border-color-inverted: var(--ui-border-inverted);
|
||||
--border-color-bg: var(--ui-bg);
|
||||
--ring-color-default: var(--ui-border);
|
||||
--ring-color-muted: var(--ui-border-muted);
|
||||
--ring-color-accented: var(--ui-border-accented);
|
||||
--ring-color-inverted: var(--ui-border-inverted);
|
||||
--ring-color-bg: var(--ui-bg);
|
||||
--ring-offset-color-default: var(--ui-border);
|
||||
--ring-offset-color-muted: var(--ui-border-muted);
|
||||
--ring-offset-color-accented: var(--ui-border-accented);
|
||||
--ring-offset-color-inverted: var(--ui-border-inverted);
|
||||
--ring-offset-color-bg: var(--ui-bg);
|
||||
--divide-color-default: var(--ui-border);
|
||||
--divide-color-muted: var(--ui-border-muted);
|
||||
--divide-color-accented: var(--ui-border-accented);
|
||||
--divide-color-inverted: var(--ui-border-inverted);
|
||||
--divide-color-bg: var(--ui-bg);
|
||||
--outline-color-default: var(--ui-border);
|
||||
--outline-color-inverted: var(--ui-border-inverted);
|
||||
--stroke-color-default: var(--ui-border);
|
||||
--stroke-color-inverted: var(--ui-border-inverted);
|
||||
--fill-color-default: var(--ui-border);
|
||||
--fill-color-inverted: var(--ui-border-inverted);
|
||||
}
|
||||
```
|
||||
:::
|
||||
|
||||
::
|
||||
|
||||
You can customize these CSS variables to tailor the appearance of your application in your `main.css` file:
|
||||
|
||||
::module-only
|
||||
#ui
|
||||
:::div{class="*:!mb-0 *:!mt-2.5"}
|
||||
|
||||
```css [app/assets/css/main.css]
|
||||
@import "tailwindcss";
|
||||
@import "@nuxt/ui";
|
||||
|
||||
:root {
|
||||
--ui-bg: var(--ui-color-neutral-50);
|
||||
--ui-text: var(--ui-color-neutral-900);
|
||||
}
|
||||
|
||||
.dark {
|
||||
--ui-bg: var(--ui-color-neutral-950);
|
||||
--ui-border: var(--ui-color-neutral-900);
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
#ui-pro
|
||||
:::div{class="*:!mb-0 *:!mt-2.5"}
|
||||
|
||||
```css [app/assets/css/main.css]
|
||||
@import "tailwindcss";
|
||||
@import "@nuxt/ui-pro";
|
||||
|
||||
:root {
|
||||
--ui-bg: var(--ui-color-neutral-50);
|
||||
--ui-text: var(--ui-color-neutral-900);
|
||||
}
|
||||
|
||||
.dark {
|
||||
--ui-bg: var(--ui-color-neutral-950);
|
||||
--ui-border: var(--ui-color-neutral-900);
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
::
|
||||
|
||||
::note
|
||||
Nuxt UI applies a text and background color on the `<body>` element of your app:
|
||||
|
||||
```css
|
||||
body {
|
||||
@apply antialiased text-(--ui-text) bg-(--ui-bg);
|
||||
@apply antialiased text-default bg-default scheme-light dark:scheme-dark;
|
||||
}
|
||||
```
|
||||
|
||||
::
|
||||
|
||||
::tip
|
||||
You can customize these CSS variables to tailor the appearance of your application:
|
||||
### Radius
|
||||
|
||||
::module-only
|
||||
#ui
|
||||
:::div{class="*:!mb-0 *:!mt-2.5"}
|
||||
|
||||
```css [app/assets/css/main.css]
|
||||
@import "tailwindcss";
|
||||
@import "@nuxt/ui";
|
||||
|
||||
:root {
|
||||
--ui-bg: var(--ui-color-neutral-50);
|
||||
--ui-text: var(--ui-color-neutral-900);
|
||||
}
|
||||
|
||||
.dark {
|
||||
--ui-bg: var(--ui-color-neutral-950);
|
||||
--ui-border: var(--ui-color-neutral-900);
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
#ui-pro
|
||||
:::div{class="*:!mb-0 *:!mt-2.5"}
|
||||
|
||||
```css [app/assets/css/main.css]
|
||||
@import "tailwindcss";
|
||||
@import "@nuxt/ui-pro";
|
||||
|
||||
:root {
|
||||
--ui-bg: var(--ui-color-neutral-50);
|
||||
--ui-text: var(--ui-color-neutral-900);
|
||||
}
|
||||
|
||||
.dark {
|
||||
--ui-bg: var(--ui-color-neutral-950);
|
||||
--ui-border: var(--ui-color-neutral-900);
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
::
|
||||
|
||||
::
|
||||
|
||||
#### Border Radius
|
||||
|
||||
Nuxt UI uses a global `--ui-radius` CSS variable for consistent border rounding. Components use variations of this base value, like `rounded-[calc(var(--ui-radius)*2)]`, to create different levels of roundness throughout the UI:
|
||||
Nuxt UI provides a centralized border radius system through the `--ui-radius` CSS variable.
|
||||
|
||||
```css
|
||||
:root {
|
||||
--ui-radius: var(--radius-sm);
|
||||
--ui-radius: 0.25rem;
|
||||
}
|
||||
```
|
||||
|
||||
::note
|
||||
Try the :prose-icon{name="i-lucide-swatch-book" class="text-(--ui-primary)"} theme picker in the header above to change the base radius value.
|
||||
This CSS variable replaces Tailwind CSS's default `rounded-*` utilities so you can use the same class names:
|
||||
|
||||
::code-preview
|
||||
[xs]{class="border-2 border-accented text-sm px-4 py-1.5 inline-block rounded-xs mr-2"}
|
||||
[sm]{class="border-2 border-accented text-sm px-4 py-1.5 inline-block rounded-sm mr-2"}
|
||||
[md]{class="border-2 border-accented text-sm px-4 py-1.5 inline-block rounded-md mr-2"}
|
||||
[lg]{class="border-2 border-accented text-sm px-4 py-1.5 inline-block rounded-lg mr-2"}
|
||||
[xl]{class="border-2 border-accented text-sm px-4 py-1.5 inline-block rounded-xl mr-2"}
|
||||
[2xl]{class="border-2 border-accented text-sm px-4 py-1.5 inline-block rounded-2xl mr-2"}
|
||||
[3xl]{class="border-2 border-accented text-sm px-4 py-1.5 inline-block rounded-3xl mr-2"}
|
||||
|
||||
#code
|
||||
```vue
|
||||
<template>
|
||||
<div class="rounded-xs">xs</div>
|
||||
<div class="rounded-sm">sm</div>
|
||||
<div class="rounded-md">md</div>
|
||||
<div class="rounded-lg">lg</div>
|
||||
<div class="rounded-xl">xl</div>
|
||||
<div class="rounded-2xl">2xl</div>
|
||||
<div class="rounded-3xl">3xl</div>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
::tip
|
||||
You can customize the default radius value using the default Tailwind CSS variables or a value of your choice:
|
||||
::note
|
||||
This is how the `@theme` is generated for each radius value:
|
||||
|
||||
:::code-collapse{class="[&>div]:!my-0"}
|
||||
```scss
|
||||
@theme default {
|
||||
--radius-xs: calc(var(--ui-radius) * 0.5); /* 0.125rem */
|
||||
--radius-sm: var(--ui-radius); /* 0.25rem */
|
||||
--radius-md: calc(var(--ui-radius) * 1.5); /* 0.375rem */
|
||||
--radius-lg: calc(var(--ui-radius) * 2); /* 0.5rem */
|
||||
--radius-xl: calc(var(--ui-radius) * 3); /* 0.75rem */
|
||||
--radius-2xl: calc(var(--ui-radius) * 4); /* 1rem */
|
||||
--radius-3xl: calc(var(--ui-radius) * 6); /* 1.5rem */
|
||||
}
|
||||
```
|
||||
:::
|
||||
|
||||
::
|
||||
|
||||
You can customize the base radius value in your `main.css` file:
|
||||
|
||||
::module-only
|
||||
#ui
|
||||
@@ -629,7 +879,7 @@ You can customize the default radius value using the default Tailwind CSS variab
|
||||
@import "@nuxt/ui";
|
||||
|
||||
:root {
|
||||
--ui-radius: var(--radius-sm);
|
||||
--ui-radius: 0.5rem;
|
||||
}
|
||||
```
|
||||
|
||||
@@ -643,18 +893,20 @@ You can customize the default radius value using the default Tailwind CSS variab
|
||||
@import "@nuxt/ui-pro";
|
||||
|
||||
:root {
|
||||
--ui-radius: var(--radius-sm);
|
||||
--ui-radius: 0.5rem;
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
::
|
||||
|
||||
::note
|
||||
Try the :prose-icon{name="i-lucide-swatch-book" class="text-primary"} theme picker in the header above to change the base radius value.
|
||||
::
|
||||
|
||||
#### Container
|
||||
### Container
|
||||
|
||||
Nuxt UI uses a global `--ui-container` CSS variable to define the width of the container:
|
||||
Nuxt UI provides a `--ui-container` CSS variable that controls the maximum width of the [Container](/components/container) component.
|
||||
|
||||
```css
|
||||
:root {
|
||||
@@ -662,8 +914,7 @@ Nuxt UI uses a global `--ui-container` CSS variable to define the width of the c
|
||||
}
|
||||
```
|
||||
|
||||
::tip
|
||||
You can customize the default container width using the default Tailwind CSS variables or a value of your choice:
|
||||
You can customize this value in your `main.css` file to adjust container widths consistently throughout your application:
|
||||
|
||||
::module-only
|
||||
#ui
|
||||
@@ -703,8 +954,6 @@ You can customize the default container width using the default Tailwind CSS var
|
||||
:::
|
||||
::
|
||||
|
||||
::
|
||||
|
||||
## Components theme
|
||||
|
||||
Nuxt UI components are styled using the [Tailwind Variants](https://www.tailwind-variants.org/) API, which provides a powerful way to create variants and manage component styles. Let's explore the key features of this API:
|
||||
@@ -718,7 +967,7 @@ Components in Nuxt UI can have multiple `slots`, each representing a distinct HT
|
||||
```ts [src/theme/card.ts]
|
||||
export default {
|
||||
slots: {
|
||||
root: 'bg-(--ui-bg) ring ring-(--ui-border) divide-y divide-(--ui-border) rounded-[calc(var(--ui-radius)*2)]',
|
||||
root: 'bg-default ring ring-default divide-y divide-default rounded-lg',
|
||||
header: 'p-4 sm:px-6',
|
||||
body: 'p-4 sm:p-6',
|
||||
footer: 'p-4 sm:px-6'
|
||||
@@ -728,7 +977,7 @@ export default {
|
||||
|
||||
```vue [src/runtime/components/Card.vue]
|
||||
<template>
|
||||
<div :class="ui.root({ class: [props.class, props.ui?.root] })">
|
||||
<div :class="ui.root({ class: [props.ui?.root, props.class] })">
|
||||
<div :class="ui.header({ class: props.ui?.header })">
|
||||
<slot name="header" />
|
||||
</div>
|
||||
@@ -777,7 +1026,7 @@ Nuxt UI components use `variants` to change the `slots` styles based on props. H
|
||||
```ts [src/theme/avatar.ts]
|
||||
export default {
|
||||
slots: {
|
||||
root: 'inline-flex items-center justify-center shrink-0 select-none overflow-hidden rounded-full align-middle bg-(--ui-bg-elevated)',
|
||||
root: 'inline-flex items-center justify-center shrink-0 select-none overflow-hidden rounded-full align-middle bg-elevated',
|
||||
image: 'h-full w-full rounded-[inherit] object-cover'
|
||||
},
|
||||
variants: {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user