Compare commits

...

163 Commits

Author SHA1 Message Date
Benjamin Canac
cc6db6f14b chore(release): v3.0.0-alpha.4 2024-10-01 15:05:23 +02:00
Benjamin Canac
1f9abdae61 fix(Toast): improve focus styles 2024-10-01 14:50:27 +02:00
Benjamin Canac
f54f607413 fix(build.config): disable mkdist addRelativeDeclarationExtensions option 2024-10-01 14:41:19 +02:00
Benjamin Canac
08db1c68b3 docs(command-palette): update 2024-10-01 11:43:47 +02:00
Benjamin Canac
725833c4a9 cli: fix paths 2024-10-01 11:43:34 +02:00
Benjamin Canac
6a6c43378b chore(CHANGELOG): update from dev 2024-09-30 14:36:51 +02:00
Benjamin Canac
10ab4ce06d docs(composables): clean 2024-09-30 14:33:07 +02:00
Benjamin Canac
afd9b8f2b9 docs(command-palette): use index in example to prevent ts error for now 2024-09-30 14:30:42 +02:00
Benjamin Canac
6aac821e80 docs(roadmap): invalid appConfig access 2024-09-30 14:30:21 +02:00
Benjamin Canac
f4e135d07a test(Drawer): types 2024-09-30 12:48:16 +02:00
Benjamin Canac
ef4a3a09c9 docs(app): put back community links 2024-09-30 12:06:37 +02:00
Benjamin Canac
c738bd9d53 docs(composables): update 2024-09-30 12:01:35 +02:00
Benjamin Canac
aeccf30a92 docs(getting-started): update 2024-09-30 11:44:39 +02:00
Benjamin Canac
673fef1595 docs(nuxt.config): set icon.provider to iconify 2024-09-30 11:12:08 +02:00
renovate[bot]
0ed13f33b5 chore(deps): update all non-major dependencies (v3) (#2265)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-30 10:22:11 +02:00
Maxime Pauvert
30c33c7113 fix(README): npm badge link (#2271) 2024-09-30 09:34:26 +02:00
renovate[bot]
fd0ecc6d96 chore(deps): lock file maintenance (v3) (#2279)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-30 09:33:49 +02:00
Benjamin Canac
6863254d89 docs(toast): update 2024-09-27 15:37:26 +02:00
Benjamin Canac
d2e075bb4a docs(ComponentExample): improve options handling 2024-09-27 15:37:15 +02:00
Benjamin Canac
6f0b7309c9 docs(drawer): update description 2024-09-27 13:08:15 +02:00
Benjamin Canac
30d9a2653b chore(Link): allow title field 2024-09-27 12:49:32 +02:00
Benjamin Canac
fac52fa933 fix(Drawer): improve max-width on mobile 2024-09-27 12:21:29 +02:00
Benjamin Canac
5f77aac368 feat(Drawer): handle direction + handle props 2024-09-27 11:44:52 +02:00
Benjamin Canac
5b62a8c8ca docs(pagination): update 2024-09-26 17:00:54 +02:00
Benjamin Canac
04a2f2b7a0 chore(Pagination): update defaults 2024-09-26 17:00:43 +02:00
Benjamin Canac
ad3dc26e41 docs(ComponentCode): fix display of false value in select 2024-09-26 16:35:54 +02:00
Benjamin Canac
fd6c1b02ea docs(ComponentCode): handle model prop for cases like v-model:page 2024-09-26 16:21:07 +02:00
Benjamin Canac
19362a9302 chore(deps): update 2024-09-26 16:18:59 +02:00
renovate[bot]
4e2b957882 chore(deps): update dependency @nuxt/fonts to ^0.9.2 (v3) (#2250)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-26 15:30:03 +02:00
Benjamin Canac
83ee05dd0e chore(renovate): update 2024-09-26 15:12:25 +02:00
Benjamin Canac
5591854ed4 docs(getting-started): update description 2024-09-26 12:04:24 +02:00
Benjamin Canac
db754740ef docs(input-menu): update 2024-09-26 12:04:12 +02:00
Benjamin Canac
2c7c41bd04 fix(InputMenu): missing group on trailing 2024-09-26 12:00:06 +02:00
Benjamin Canac
9edb23080d docs(define-shortcuts): update 2024-09-25 19:22:44 +02:00
Benjamin Canac
fb8e6a6a66 docs(select-menu): update 2024-09-25 19:07:21 +02:00
Benjamin Canac
183207c2b8 chore(SelectMenu): merge search / searchPlaceholder into searchInput prop 2024-09-25 19:07:11 +02:00
Benjamin Canac
237e7a4e0e docs(nuxt.config): move routeRules cache in $production 2024-09-25 16:56:22 +02:00
Benjamin Canac
cac41137ae docs(getting-started): update 2024-09-25 16:42:40 +02:00
Benjamin Canac
eb0756674a docs(nuxt.config): put back /api/* in cloudflare exclude 2024-09-25 12:34:01 +02:00
Benjamin Canac
9cb863bc21 docs(nuxt.config): remove /api/* in cloudflare exclude 2024-09-25 12:25:27 +02:00
Benjamin Canac
9125a2d467 docs(nuxt.config): try icon serverBundle to remote 2024-09-25 12:19:28 +02:00
Benjamin Canac
0297361319 docs(nuxt.config): update icon options 2024-09-25 12:06:29 +02:00
Sébastien Chopin
749bf90dd1 docs: add auto sub folder 2024-09-25 11:36:05 +02:00
Benjamin Canac
d283887407 docs(nuxt.config): remove experimental.buildCache 2024-09-25 11:07:01 +02:00
Benjamin Canac
d7e46e3637 docs(nuxt.config): remove og-image from cloudflare option 2024-09-25 10:54:41 +02:00
Pooya Parsa
713021f12d docs: enable crawlLinks with exclude patterns (#2246) 2024-09-25 10:44:24 +02:00
Benjamin Canac
1aff74e985 docs(app): update title template 2024-09-25 10:44:05 +02:00
Benjamin Canac
5b3cda741c docs(Header): responsive 2024-09-24 23:10:24 +02:00
Benjamin Canac
a61e7656c2 fix(CommandPalette): missing min-w-0 on root 2024-09-24 23:06:02 +02:00
Benjamin Canac
85b21381e8 docs(app): update loading indicator color 2024-09-24 22:58:34 +02:00
Benjamin Canac
44b98bc7c9 docs(app): use navigation?.title in metas 2024-09-24 22:58:23 +02:00
Benjamin Canac
6c285977bd fix(Accordion): missing min-w-0 on trigger 2024-09-24 22:14:04 +02:00
Benjamin Canac
4c39fa8b0f docs(nuxt.config): add cache 2024-09-24 18:47:13 +02:00
Benjamin Canac
e584941df9 docs: add @nuxthub/core module 2024-09-24 18:28:01 +02:00
Benjamin Canac
991e725b1a docs(nuxt.config): try without crawLinks 2024-09-24 18:16:03 +02:00
Benjamin Canac
b638521ffa docs(public): remove illustrations 2024-09-24 18:08:02 +02:00
Benjamin Canac
2ec978ed0d docs(getting-started): use Public sans to prevent bundle of Inter 2024-09-24 18:02:23 +02:00
Benjamin Canac
b7f9422091 docs: make generate work 2024-09-24 17:53:21 +02:00
Benjamin Canac
d921b764de docs(ComponentCode/ComponentExample): wrong border on light mode 2024-09-24 14:58:40 +02:00
Benjamin Canac
b83ecc9a6f Revert "chore(deps): refresh lock"
This reverts commit cfe4e0bd65.
2024-09-24 14:47:31 +02:00
Benjamin Canac
40f1161b04 docs(app): update font 2024-09-24 14:43:45 +02:00
Benjamin Canac
d419f0a9cb playground(app): update font 2024-09-24 14:43:35 +02:00
Benjamin Canac
7bd06ecfd9 docs(prettier): format in worker like on ssr 2024-09-24 14:43:19 +02:00
Benjamin Canac
cac33c87b2 chore(InputMenu/SelectMenu): handle item select 2024-09-24 14:42:40 +02:00
Benjamin Canac
5e55e15ddf chore(InputMenu/Progress/SelectMenu): clean interfaces for syntax highlight 2024-09-24 14:42:40 +02:00
Benjamin Canac
09d453b5cf docs(ComponentCode/ComponentExample): use justify-center everywhere 2024-09-23 17:16:34 +02:00
Benjamin Canac
4f5a8ee4f6 docs(installation): fix theme.colors option example 2024-09-23 17:14:14 +02:00
Benjamin Canac
cfe4e0bd65 chore(deps): refresh lock 2024-09-23 11:59:32 +02:00
renovate[bot]
1932aa2671 chore(deps): update all non-major dependencies (v3) (#2230)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-23 11:45:16 +02:00
Anthony Fu
cc8fa471e9 chore: replace dev time flag for build (#2231)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2024-09-23 11:00:51 +02:00
Benjamin Canac
19bfc36976 test(Input): add missing file type 2024-09-23 10:52:02 +02:00
Benjamin Canac
7d754c0015 chore(components): add newline before script to correct syntax highlight 2024-09-20 23:20:39 +02:00
Benjamin Canac
8709717a32 docs(command-palette): update 2024-09-20 18:45:52 +02:00
Benjamin Canac
32ac575d56 chore(README): update 2024-09-20 17:54:30 +02:00
Benjamin Canac
c39996c0fe docs(app): increase container size 2024-09-20 15:39:47 +02:00
renovate[bot]
235f1cc330 chore(deps): update all non-major dependencies (v3) (#2219)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2024-09-20 14:16:59 +02:00
Benjamin Canac
f45b39937b docs(command-palette): update 2024-09-18 17:32:01 +02:00
Benjamin Canac
a70ba04fc3 chore(Alert): rename close event to update:open for consistency 2024-09-18 17:31:42 +02:00
Benjamin Canac
500b4727e8 docs(ComponentExample): update 2024-09-18 15:00:23 +02:00
Benjamin Canac
d36576b1fe chore(module): gray now defaults to slate 2024-09-18 12:21:50 +02:00
Benjamin Canac
6498f8a0c1 test: move colors into theme.colors 2024-09-18 12:20:12 +02:00
Benjamin Canac
96c9246d83 fix(templates): app config colors type 2024-09-18 12:19:46 +02:00
Benjamin Canac
e45398f6ce docs(playground): update theme 2024-09-18 12:14:33 +02:00
Benjamin Canac
a32d2c034a docs(app): update theme 2024-09-18 12:14:23 +02:00
Benjamin Canac
f52310d4a1 docs(deps): use @nuxt/ui-pro 2024-09-18 11:33:56 +02:00
Benjamin Canac
a6ae1dcb1d chore(release): v3.0.0-alpha.3 2024-09-18 11:31:39 +02:00
Benjamin Canac
2e954467c4 feat(module): move colors options into theme.colors 2024-09-18 11:28:31 +02:00
Benjamin Canac
d3317d828e docs: use shiki-transformer-color-highlight (#2215) 2024-09-18 10:54:59 +02:00
Benjamin Canac
2c3032e363 chore(release): v3.0.0-alpha.2 2024-09-18 10:32:18 +02:00
Romain Hamel
dd6bf5694f fix(Button): duplicate click handlers (#2213)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2024-09-18 10:18:32 +02:00
Benjamin Canac
a8b26eb1a8 playground(link): fix urls 2024-09-17 18:34:33 +02:00
Benjamin Canac
df72003516 docs(getting-started): update 2024-09-17 16:39:45 +02:00
Benjamin Canac
1f5372baba docs(app): display pages descriptions in markdown 2024-09-17 16:36:00 +02:00
Benjamin Canac
d4efd13307 docs(IconsTheme): omit @nuxt/ui-pro icons 2024-09-17 16:35:47 +02:00
Benjamin Canac
87c0a0f9c1 chore(package): add missing description 2024-09-17 12:33:55 +02:00
Benjamin Canac
d5339d3ebe docs(ComponentCode): fix boolean type 2024-09-17 11:36:44 +02:00
Benjamin Canac
ff8eefdce7 chore(theme): clean Select & InputMenu 2024-09-17 11:25:35 +02:00
Benjamin Canac
2346f52cbf chore(components): missing some props.ui merging 2024-09-17 11:25:16 +02:00
Benjamin Canac
d0a669ee2e chore(deps): update 2024-09-16 12:52:48 +02:00
Benjamin Canac
7e672bd041 docs: ts errors 2024-09-16 12:51:36 +02:00
Romain Hamel
ed18e74549 feat(Button): loading-auto (#2198) 2024-09-16 11:37:38 +02:00
Benjamin Canac
6f20f243fb docs(getting-started): update 2024-09-15 21:44:10 +02:00
Benjamin Canac
ce91b5d75a docs(command-palette): update 2024-09-12 18:29:23 +02:00
Benjamin Canac
d9b14bc325 docs(navigation-menu): remove useless classes 2024-09-12 18:29:13 +02:00
Benjamin Canac
d0ac6f95a4 docs(getting-started): update 2024-09-12 18:29:01 +02:00
Benjamin Canac
8f76caa7f8 docs(ComponentCode): add slug prop 2024-09-12 18:27:15 +02:00
Benjamin Canac
336631434c docs(deps): update nuxt-component-meta 2024-09-12 16:45:29 +02:00
Romain Hamel
319fce136f docs(form): update (#2167)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2024-09-12 15:47:58 +02:00
Benjamin Canac
1667e5a655 chore(deps): set nuxt-component-meta resolution 2024-09-12 15:22:30 +02:00
Benjamin Canac
ad77a0e82d docs: fix refactor 2024-09-12 15:22:10 +02:00
Benjamin Canac
5076b8cc9e feat(module): improve options 2024-09-12 12:43:12 +02:00
Benjamin Canac
2fc6e18179 docs(getting-started): update 2024-09-12 12:42:23 +02:00
Benjamin Canac
8898a5d675 feat(module): install @nuxt/fonts by default 2024-09-12 12:42:23 +02:00
Bernardo Oliveira
7c2adf2f7f fix(Button): button link not showing disabled classes (#2189) 2024-09-12 12:25:24 +02:00
Romain Hamel
523493105e docs: types (#2186) 2024-09-12 11:10:23 +02:00
renovate[bot]
26e5ac80b6 chore(deps): update all non-major dependencies (v3) (#2184)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-12 10:44:19 +02:00
Benjamin Canac
a83e3821a4 docs(getting-started): update 2024-09-11 19:20:54 +02:00
Benjamin Canac
ea8a083d84 docs(index): update 2024-09-11 18:56:13 +02:00
Romain Hamel
cf92c5f3f0 fix(playground): typecheck 2024-09-11 18:53:19 +02:00
renovate[bot]
594bc9bb2e chore(deps): update all non-major dependencies (v3) (#2182)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-11 16:39:56 +02:00
Benjamin Canac
7e87f05840 docs(nuxt.config): remove prerender 2024-09-11 16:02:42 +02:00
Benjamin Canac
942f087a67 docs(nuxt.config): prerender routes 2024-09-11 12:24:52 +02:00
Benjamin Canac
13975da684 docs(deps): update @nuxt/ui-pro 2024-09-11 11:49:56 +02:00
Benjamin Canac
4198a42dff chore(release): v3.0.0-alpha.1 2024-09-11 10:38:54 +02:00
Benjamin Canac
21609aa884 chore(package): update release script 2024-09-11 10:36:54 +02:00
Benjamin Canac
82771673f2 fix(types): no longer need to import types with /index suffix
Solved in https://github.com/unjs/mkdist/pull/244
2024-09-11 10:34:59 +02:00
Benjamin Canac
4f83a2829e chore(deps): update 2024-09-11 10:34:21 +02:00
Benjamin Canac
debf9cc853 fix(plugins): infer type from #app to remove build warning 2024-09-11 10:30:15 +02:00
Benjamin Canac
40b3570343 fix(templates): augment @nuxt/schema rather than nuxt/schema 2024-09-10 17:01:39 +02:00
Benjamin Canac
95d2b7a56e docs(Banner): update icon 2024-09-10 16:26:26 +02:00
Benjamin Canac
c6171d0f8d docs: use Inter font 2024-09-10 16:26:19 +02:00
Benjamin Canac
625c4efa03 docs(installation): update 2024-09-10 15:37:41 +02:00
Benjamin Canac
d8d7b8fcc5 docs(components): update github links to nuxt/ui repository 2024-09-10 15:34:09 +02:00
Benjamin Canac
c578acbb88 chore(deps): refresh lock 2024-09-10 15:00:50 +02:00
Benjamin Canac
3e30775fd4 docs(IconsTheme): format without prettier to improve performances 2024-09-10 15:00:50 +02:00
Benjamin Canac
ec84f777ea docs(radio-group): link value to Value Key title 2024-09-10 15:00:50 +02:00
Benjamin Canac
a2a303f527 docs(HighlightInlintType): hide undefined in types 2024-09-10 15:00:50 +02:00
Benjamin Canac
3d0e1eb288 docs(components): ignore prefetchOn prop 2024-09-10 15:00:50 +02:00
Romain Hamel
175229384f chore(Form): catch-up with v2 changes (#2165)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2024-09-10 14:09:42 +02:00
renovate[bot]
9ddfec123e chore(deps): update pnpm to v9.10.0 (v3) (#2164)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-09 18:29:29 +02:00
Benjamin Canac
1f0e515c33 chore(App): types 2024-09-09 18:12:23 +02:00
Benjamin Canac
3e28c8f35a fix: import from ../types/index 2024-09-09 18:00:39 +02:00
Benjamin Canac
9d8bb80892 chore(deps): refresh lock 2024-09-09 15:39:50 +02:00
Benjamin Canac
8b8ec22af7 docs(deps): update nuxt-component-meta 2024-09-09 13:53:32 +02:00
Benjamin Canac
4b09358ce0 chore(deps): update 2024-09-09 12:22:41 +02:00
Benjamin Canac
4f339be363 playground(app): improve from @nuxt/ui-pro 2024-09-06 19:02:31 +02:00
Benjamin Canac
7d28d99f10 playground(app): improve from @nuxt/ui 2024-09-06 19:02:31 +02:00
Benjamin Canac
71428da3dc fix(README): license link 2024-09-06 19:02:31 +02:00
Benjamin Canac
4a7433d628 chore(package): export style 2024-09-06 19:02:31 +02:00
Romain Hamel
8c886279b2 test(Form): fix (#2153) 2024-09-06 18:58:24 +02:00
Benjamin Canac
62a2643a80 feat(module): hard-code css file to be imported anywhere 2024-09-06 17:27:06 +02:00
Benjamin Canac
7cd3cc4aa6 chore(module): update nuxt compatibility 2024-09-06 17:26:43 +02:00
Benjamin Canac
07b0cf9979 chore(deps): update 2024-09-06 16:51:51 +02:00
Benjamin Canac
c94041d656 chore(github): update workflows 2024-09-06 15:27:11 +02:00
Benjamin Canac
97d05936cd fix(useButtonGroup): lint 2024-09-06 15:22:07 +02:00
Benjamin Canac
a03a55cf8d fix(ContextMenu/DropdownMenu): lint unused var 2024-09-06 15:21:56 +02:00
Benjamin Canac
1716ba0f5e docs(navigation-menu): update 2024-09-06 14:59:05 +02:00
Benjamin Canac
a8b5bff8c6 docs(ComponentExample): update 2024-09-06 13:00:23 +02:00
Benjamin Canac
84186e52e9 fix(NavigationMenu): handle open state hover effect 2024-09-06 13:00:05 +02:00
Benjamin Canac
7fe7ff6fe2 fix(Link): only bind necessary slot props 2024-09-06 12:57:04 +02:00
Benjamin Canac
aa34e3c141 chore(package): remove engines 2024-09-06 11:57:52 +02:00
Benjamin Canac
d989ceb09d chore(vercel): disable deployments 2024-09-06 09:53:19 +02:00
Benjamin Canac
403d3b2718 docs(deps): use @nuxt/ui-pro@2.0.0-alpha.1 2024-09-06 09:53:06 +02:00
276 changed files with 12557 additions and 6901 deletions

View File

@@ -1,72 +0,0 @@
name: ci-dev
on:
push:
branches:
- dev
pull_request:
branches:
- dev
jobs:
ci:
runs-on: ${{ matrix.os }}
permissions:
contents: read
pull-requests: read
strategy:
matrix:
os: [ubuntu-latest] # macos-latest, windows-latest
node: [20]
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: Filter changes
uses: dorny/paths-filter@v3
id: changes
with:
filters: |
src:
- 'src/**'
- 'package.json'
- 'pnpm-lock.yaml'
- 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: Build
run: pnpm run build
- name: Release Edge
if: github.event_name == 'push' && steps.changes.outputs.src == 'true'
run: ./scripts/release-edge.sh
env:
NODE_AUTH_TOKEN: ${{secrets.NODE_AUTH_TOKEN}}

View File

@@ -37,16 +37,6 @@ jobs:
node-version: ${{ matrix.node }}
cache: pnpm
- name: Filter changes
uses: dorny/paths-filter@v3
id: changes
with:
filters: |
src:
- 'src/**'
- 'package.json'
- 'pnpm-lock.yaml'
- name: Install dependencies
run: pnpm install

View File

@@ -1,61 +0,0 @@
name: ci-main
on:
push:
branches:
- main
jobs:
ci:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest] # macos-latest, windows-latest
node: [20]
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: Build
run: pnpm run build
- name: Version Check
id: check
uses: EndBug/version-check@v2
with:
token: ${{ secrets.GITHUB_TOKEN }}
- name: Release
if: github.event_name == 'push' && steps.check.outputs.changed == 'true'
run: ./scripts/release.sh
env:
NODE_AUTH_TOKEN: ${{ secrets.NODE_AUTH_TOKEN }}

View File

@@ -1,5 +1,72 @@
# Changelog
## [3.0.0-alpha.4](https://github.com/nuxt/ui/compare/v3.0.0-alpha.3...v3.0.0-alpha.4) (2024-10-01)
### Features
* **Drawer:** handle `direction` + `handle` props ([5f77aac](https://github.com/nuxt/ui/commit/5f77aac368448c7c45a0f9238d2dc3a5b0de825e))
### Bug Fixes
* **Accordion:** missing `min-w-0` on trigger ([6c28597](https://github.com/nuxt/ui/commit/6c285977bd175d4866ca601bca47132ebb2d3440))
* **build.config:** disable mkdist `addRelativeDeclarationExtensions` option ([f54f607](https://github.com/nuxt/ui/commit/f54f6074131db0f68eab1edcde3a4b2a7ecaba92))
* **CommandPalette:** missing `min-w-0` on root ([a61e765](https://github.com/nuxt/ui/commit/a61e7656c25b26409cab77178e67d1cb9ec22dbd))
* **Drawer:** improve max-width on mobile ([fac52fa](https://github.com/nuxt/ui/commit/fac52fa933aeb02f0855d20be37c4214efba0ab7))
* **InputMenu:** missing `group` on trailing ([2c7c41b](https://github.com/nuxt/ui/commit/2c7c41bd046a961d398bbe8ee4a5945cd1fbaeab))
* **README:** npm badge link ([#2271](https://github.com/nuxt/ui/issues/2271)) ([30c33c7](https://github.com/nuxt/ui/commit/30c33c71134ccbea4258949a851eaf8b26213b60))
* **templates:** app config colors type ([96c9246](https://github.com/nuxt/ui/commit/96c9246d83b54637ceb2e2dd77542e435690c387))
* **Toast:** improve focus styles ([1f9abda](https://github.com/nuxt/ui/commit/1f9abdae614acbfa0be868a599071a601406f0f5))
### Reverts
* Revert "chore(deps): refresh lock" ([b83ecc9](https://github.com/nuxt/ui/commit/b83ecc9a6f309d37d3f096667143a4ed7700db6d))
## [3.0.0-alpha.3](https://github.com/nuxt/ui/compare/v3.0.0-alpha.2...v3.0.0-alpha.3) (2024-09-18)
### Features
* **module:** move `colors` options into `theme.colors` ([2e95446](https://github.com/nuxt/ui/commit/2e954467c4679d70b68d3155ae34eca300508e38))
## [3.0.0-alpha.2](https://github.com/nuxt/ui/compare/v3.0.0-alpha.1...v3.0.0-alpha.2) (2024-09-18)
### Features
* **Button:** loading-auto ([#2198](https://github.com/nuxt/ui/issues/2198)) ([ed18e74](https://github.com/nuxt/ui/commit/ed18e7454986ed104fc73b77e88573b3c1df8566))
* **module:** improve options ([5076b8c](https://github.com/nuxt/ui/commit/5076b8cc9e908cf289150c668b1707dc1397dba3))
* **module:** install `@nuxt/fonts` by default ([8898a5d](https://github.com/nuxt/ui/commit/8898a5d6758b1047e35bcdf648362c42de387488))
### Bug Fixes
* **Button:** button link not showing disabled classes ([#2189](https://github.com/nuxt/ui/issues/2189)) ([7c2adf2](https://github.com/nuxt/ui/commit/7c2adf2f7fc88174897cc775c752414a8b84f3a9))
* **Button:** duplicate click handlers ([#2213](https://github.com/nuxt/ui/issues/2213)) ([dd6bf56](https://github.com/nuxt/ui/commit/dd6bf5694ff05ed1eeb9df8c42f833f51dbec66e))
* **playground:** typecheck ([cf92c5f](https://github.com/nuxt/ui/commit/cf92c5f3f0e0f329844ee60772773a844ea1cc71))
## [3.0.0-alpha.1](https://github.com/nuxt/ui/compare/v3.0.0-alpha.0...v3.0.0-alpha.1) (2024-09-11)
### Features
* **module:** hard-code css file to be imported anywhere ([62a2643](https://github.com/nuxt/ui/commit/62a2643a80e7ab6c6e154ba59801d393d9a53c40))
### Bug Fixes
* **ContextMenu/DropdownMenu:** lint unused var ([a03a55c](https://github.com/nuxt/ui/commit/a03a55cf8d89c45fba6607f83b67367cfd419c3e))
* import from `../types/index` ([3e28c8f](https://github.com/nuxt/ui/commit/3e28c8f35a64a7c19ce18f36dbe580503f2050bc))
* **Link:** only bind necessary slot props ([7fe7ff6](https://github.com/nuxt/ui/commit/7fe7ff6fe2055d29b7fd54793ca52850842294e3))
* **NavigationMenu:** handle open state hover effect ([84186e5](https://github.com/nuxt/ui/commit/84186e52e997a4dd55f98bf7bc0199656943b9c9))
* **plugins:** infer type from `[#app](https://github.com/nuxt/ui/issues/app)` to remove build warning ([debf9cc](https://github.com/nuxt/ui/commit/debf9cc85339b7b162ac34392757214a16dad977))
* **README:** license link ([71428da](https://github.com/nuxt/ui/commit/71428da3dc9c6f17a6e21b2bd889f6090be127d6))
* **templates:** augment `@nuxt/schema` rather than `nuxt/schema` ([40b3570](https://github.com/nuxt/ui/commit/40b3570343dc68684d3ecf03e1a439e815f57ba3))
* **types:** no longer need to import types with `/index` suffix ([8277167](https://github.com/nuxt/ui/commit/82771673f20b6ece7e126a4f8914311473d687e3))
* **useButtonGroup:** lint ([97d0593](https://github.com/nuxt/ui/commit/97d05936cd198026e6c4d66920266e0b4b85242c))
## [3.0.0-alpha.0](https://github.com/nuxt/ui/compare/v2.15.0...v3.0.0-alpha.0) (2024-09-05)
@@ -247,6 +314,195 @@
* **types:** useless import ([5f7872f](https://github.com/nuxt/ui/commit/5f7872f06e81e03443e2d1c27a654cfe32c55fb3))
* **useComponentIcons:** reactivity when using `defu` ([45454fa](https://github.com/nuxt/ui/commit/45454fae45b8571a9691284bd6a13a838e8ea1c9))
## [2.18.6](https://github.com/nuxt/ui/compare/v2.18.5...v2.18.6) (2024-09-23)
### Bug Fixes
* **components:** accept partial config in `ui` prop ([#2235](https://github.com/nuxt/ui/issues/2235)) ([eecf4f7](https://github.com/nuxt/ui/commit/eecf4f7ed8a32a874f00afd7bff2964a1366e0b5))
* **Modal/Slideover:** bind transition class to `TransitionChild` for Vue 3.5 ([#2227](https://github.com/nuxt/ui/issues/2227)) ([803c20a](https://github.com/nuxt/ui/commit/803c20ad92e8a31fefd6d300856735b0e9adbdf9))
* **SelectMenu:** wrong placeholder color with multiple ([#2218](https://github.com/nuxt/ui/issues/2218)) ([28ad5cf](https://github.com/nuxt/ui/commit/28ad5cf98251c6a8acec8d0bf4f0fd07ff6b7066))
* **Table:** colspan with expand ([#2217](https://github.com/nuxt/ui/issues/2217)) ([56118c4](https://github.com/nuxt/ui/commit/56118c4a794f3d763dad7b65e044814cf7ef11cf))
* **Tabs:** handle icon `margin` in RTL mode ([#2233](https://github.com/nuxt/ui/issues/2233)) ([ea05414](https://github.com/nuxt/ui/commit/ea05414930fe3f5e6805c8aa25bbe8f746bcc86e))
* **useFormField:** optional property access ([#2226](https://github.com/nuxt/ui/issues/2226)) ([0a054a5](https://github.com/nuxt/ui/commit/0a054a52b64b4f774041c40223e18e7e056cfd80))
## [2.18.5](https://github.com/nuxt/ui/compare/v2.18.4...v2.18.5) (2024-09-18)
### Features
* **Form:** add errors slot prop ([#2188](https://github.com/nuxt/ui/issues/2188)) ([67c6a74](https://github.com/nuxt/ui/commit/67c6a74ed15db1ee8a40e9c74ecfef0d3c3e374a))
### Bug Fixes
* **Button:** button link not showing disabled classes ([#2185](https://github.com/nuxt/ui/issues/2185)) ([e8ea84a](https://github.com/nuxt/ui/commit/e8ea84a5736759d953664f8f397a2339c212b294))
* **Carousel:** remove trailing space in next button icon ([#2088](https://github.com/nuxt/ui/issues/2088)) ([1282a5f](https://github.com/nuxt/ui/commit/1282a5f6c001aa05597d458800bafcf6b6419634))
* **FormGroup:** remove id when used with `RadioGroup` ([#2152](https://github.com/nuxt/ui/issues/2152)) ([7aec42c](https://github.com/nuxt/ui/commit/7aec42ca15aaa0ccc63c520b484cba203fd3232b))
* **Input:** avoid binding value when type is `file` ([#2047](https://github.com/nuxt/ui/issues/2047)) ([82313e8](https://github.com/nuxt/ui/commit/82313e862cbf21ae631156af4cd057f1383db634))
* **module:** allow CSS variables in tailwind colors ([#2014](https://github.com/nuxt/ui/issues/2014)) ([7f50c70](https://github.com/nuxt/ui/commit/7f50c7031fecb5ab26a6d0f58b576b2fd0860487))
* **module:** augment `@nuxt/schema` rather than `nuxt/schema` ([#2171](https://github.com/nuxt/ui/issues/2171)) ([ead904f](https://github.com/nuxt/ui/commit/ead904fd2f2bbb29fd60ccde063bf02daa2cbdbb))
* **module:** consider user tailwind `configPath` for module as string ([#2074](https://github.com/nuxt/ui/issues/2074)) ([e4ba4f7](https://github.com/nuxt/ui/commit/e4ba4f7c729f99dde51891636605793864812d30))
* **Pagination:** use links on prev and next button ([#2179](https://github.com/nuxt/ui/issues/2179)) ([c850f85](https://github.com/nuxt/ui/commit/c850f85aaa40c7abbe8cc4dc1bd4705bf7677390))
* **README:** update license link ([#2154](https://github.com/nuxt/ui/issues/2154)) ([8d79eea](https://github.com/nuxt/ui/commit/8d79eea19b3276b1f1e069d33b98b311e9b91cfd))
* **Slideover:** bind `rounded` class to panel ([#2187](https://github.com/nuxt/ui/issues/2187)) ([bf32baa](https://github.com/nuxt/ui/commit/bf32baaab01dc4150622f67b3b4a8d02d21b922c))
* **Slideover:** bind `shadow` class to panel ([#2201](https://github.com/nuxt/ui/issues/2201)) ([d22526c](https://github.com/nuxt/ui/commit/d22526c0c10735a92e63b7d086e7b8534a08d768))
* **Table:** checkbox can emit the `[@select](https://github.com/select)` event ([#2072](https://github.com/nuxt/ui/issues/2072)) ([b1f691f](https://github.com/nuxt/ui/commit/b1f691f28ca8c94f6b658dcb61eeff06951bd1d0))
* **Table:** select all rows reactivity issue ([#2200](https://github.com/nuxt/ui/issues/2200)) ([68124de](https://github.com/nuxt/ui/commit/68124de5106e55cb2987a6ba4ec1120d79b51788))
* **Tabs:** recalculate marker if items change ([#2101](https://github.com/nuxt/ui/issues/2101)) ([82c4926](https://github.com/nuxt/ui/commit/82c4926c090ce7fac48022a93b1b05b877bb48dd))
* **Textarea:** resolve row count calculation errors caused by scrollbar ([#2040](https://github.com/nuxt/ui/issues/2040)) ([8210936](https://github.com/nuxt/ui/commit/8210936f22fcf6b7eb5b9711e2c29be38956b8d6))
## [2.18.4](https://github.com/nuxt/ui/compare/v2.18.3...v2.18.4) (2024-08-05)
### Bug Fixes
* **Form:** submit event data ([#2012](https://github.com/nuxt/ui/issues/2012)) ([4d61936](https://github.com/nuxt/ui/commit/4d61936e7e90b664846a8f265825612c509511d7))
* **module:** handle nested colors from ui config ([#2008](https://github.com/nuxt/ui/issues/2008)) ([1cc7e2a](https://github.com/nuxt/ui/commit/1cc7e2a306e0f3f666b9a588f6ed02e7eabc0272))
* **module:** reduce css bundle size by fixing safelist regex ([#2005](https://github.com/nuxt/ui/issues/2005)) ([8ac9ca4](https://github.com/nuxt/ui/commit/8ac9ca49789a9a7281f7a40926e7e9a8068cc395))
* **module:** suffix types imports with `/index` ([7e37668](https://github.com/nuxt/ui/commit/7e37668940d06c5aa20b60d9bfd600d50a171014)), closes [#2018](https://github.com/nuxt/ui/issues/2018)
* **Tabs:** use `nextTick` before marker calc ([#2020](https://github.com/nuxt/ui/issues/2020)) ([9c04969](https://github.com/nuxt/ui/commit/9c049690227af8aba61a1f7c002b00c5dfeb63ff))
* **useFormGroup:** app config default input size ([#2011](https://github.com/nuxt/ui/issues/2011)) ([3485092](https://github.com/nuxt/ui/commit/3485092edb55f9ef2ca038a8c137431866d6c28a))
## [2.18.3](https://github.com/nuxt/ui/compare/v2.18.2...v2.18.3) (2024-07-30)
### Bug Fixes
* **Link:** define `rel` as any ([69f605f](https://github.com/nuxt/ui/commit/69f605fa724454e4be9e4cee9666a5d57f43a129))
* **types:** only use `.ts` for index ([93ddf1d](https://github.com/nuxt/ui/commit/93ddf1d60b0ea5f99f564f3d3969c397ad91cc72))
## [2.18.2](https://github.com/nuxt/ui/compare/v2.18.1...v2.18.2) (2024-07-25)
### Bug Fixes
* **Tabs:** add missing `UIcon` import ([4fd1be2](https://github.com/nuxt/ui/commit/4fd1be28922bf39584005c14982e5cd9a7d0c624))
## [2.18.1](https://github.com/nuxt/ui/compare/v2.18.0...v2.18.1) (2024-07-25)
### Bug Fixes
* **components:** use relative imports ([ea721a3](https://github.com/nuxt/ui/commit/ea721a3705cfbcef3075f8c9c1f4acf359974597))
## [2.18.0](https://github.com/nuxt/ui/compare/v2.17.0...v2.18.0) (2024-07-25)
### ⚠ BREAKING CHANGES
* **Icon:** migrate from `@egoist/tailwindcss-icons` to new `@nuxt/icon` (#1789)
### Features
* **Checkbox/Radio/RadioGroup:** add `help` slot ([c3122f7](https://github.com/nuxt/ui/commit/c3122f776daa6d68f201f22c37e0084aac37ed06)), closes [#1957](https://github.com/nuxt/ui/issues/1957)
* **CommandPalette:** handle `static` groups ([#1458](https://github.com/nuxt/ui/issues/1458)) ([b264ad2](https://github.com/nuxt/ui/commit/b264ad2ebdc8d4ee4aab5c994df968025207021f))
* **Icon:** migrate from `@egoist/tailwindcss-icons` to new `@nuxt/icon` ([#1789](https://github.com/nuxt/ui/issues/1789)) ([c904604](https://github.com/nuxt/ui/commit/c904604c23987c2535e0e91e9c4fec50477f6b34))
* **module:** improve app config types autocomplete ([#1870](https://github.com/nuxt/ui/issues/1870)) ([3f8ea5d](https://github.com/nuxt/ui/commit/3f8ea5dbded7b6836495103739688905ff26fe22))
* **RadioGroup:** add `selected` to label slot props ([#1587](https://github.com/nuxt/ui/issues/1587)) ([d18477d](https://github.com/nuxt/ui/commit/d18477def58171d51bdb7d00e31e2807b2e7015b))
* **SelectMenu:** add selected to `label` / `leading` / `trailing` slots props ([#1349](https://github.com/nuxt/ui/issues/1349)) ([6b216ca](https://github.com/nuxt/ui/commit/6b216cab1ba3bb69cb317254dfd562ab020c5e92))
* **SelectMenu:** handle function in `showCreateOptionWhen` prop ([#1853](https://github.com/nuxt/ui/issues/1853)) ([7e974b5](https://github.com/nuxt/ui/commit/7e974b55d72b8ac0ab42ef722a2d1904c3e4e091))
* **Skeleton:** add `as` prop ([#1955](https://github.com/nuxt/ui/issues/1955)) ([bce94db](https://github.com/nuxt/ui/commit/bce94db9fdb2c29a4f2e5981e5dce49a44a4ac8a))
* **Table:** expand row ([#1036](https://github.com/nuxt/ui/issues/1036)) ([7155318](https://github.com/nuxt/ui/commit/71553180294c53024c28de9bbebf4ea69f616da7))
* **Table:** handle `rowClass` property in `columns` ([#1632](https://github.com/nuxt/ui/issues/1632)) ([748e491](https://github.com/nuxt/ui/commit/748e49175da37b85bd18d62a8455875990866d5b))
* **Tabs:** handle `icon` in items ([#1798](https://github.com/nuxt/ui/issues/1798)) ([e8eb394](https://github.com/nuxt/ui/commit/e8eb3941ad4c1c306ccbe9e11d979d5f6c808330))
### Bug Fixes
* **Accordion:** truncate buttons ([5db18c0](https://github.com/nuxt/ui/commit/5db18c00565f9d2bb9f2768c2de2ab291a55bcae)), closes [#1909](https://github.com/nuxt/ui/issues/1909)
* **Alert/Notification:** missing margin on description ([2c55fb6](https://github.com/nuxt/ui/commit/2c55fb63365ee7cc1e993ebd5aa5f83ddadcd26a)), closes [#1959](https://github.com/nuxt/ui/issues/1959)
* **Breadcrumb:** use `rotate` on rtl icon ([53003fc](https://github.com/nuxt/ui/commit/53003fcd07d67d13ada0759ff6c5cd3635fba0e3))
* **ButtonGroup/FormGroup:** pass default sizes to children ([#1875](https://github.com/nuxt/ui/issues/1875)) ([6b6b03d](https://github.com/nuxt/ui/commit/6b6b03d59f5ab3096b731c59d18a1085d25b5e8e))
* **Carousel:** remove `mix-blend-overlay` on indicators ([#1714](https://github.com/nuxt/ui/issues/1714)) ([f74f1df](https://github.com/nuxt/ui/commit/f74f1df6ca5f93e11e542245b611c1aa7c4b8308))
* **FormGroup:** don't check for `error` slot so `help` slot can render ([#1888](https://github.com/nuxt/ui/issues/1888)) ([99c52e5](https://github.com/nuxt/ui/commit/99c52e50082d5e99440894c7a077a17510f0de50))
* **InputMenu/SelectMenu:** invalid `label` with `value-attribute` and async search ([4d5f250](https://github.com/nuxt/ui/commit/4d5f2509022e4fb74fc268d5479f7cc8f0415040)), closes [#1780](https://github.com/nuxt/ui/issues/1780)
* **InputMenu/SelectMenu:** prevent double filter with async search ([e2881d3](https://github.com/nuxt/ui/commit/e2881d3801c54c49d66d41d4f0ba312a7b3ebce7)), closes [#1966](https://github.com/nuxt/ui/issues/1966)
* **Link:** allow `ariaLabel` to be picked ([720c44d](https://github.com/nuxt/ui/commit/720c44dd5ee90bb3b30aef32f01ff6eae1397aa4)), closes [#1934](https://github.com/nuxt/ui/issues/1934)
* **Progress:** pass down attrs to `<progress>` to improve accessibility ([#1881](https://github.com/nuxt/ui/issues/1881)) ([abd13f1](https://github.com/nuxt/ui/commit/abd13f1f8fd4c8b10069174534c5fdec6c83576e))
* **RadioGroup:** allow boolean in `modelValue` prop ([#1913](https://github.com/nuxt/ui/issues/1913)) ([8eca5a0](https://github.com/nuxt/ui/commit/8eca5a0d627e22f42350a060f09c4e44b6de422f))
## [2.17.0](https://github.com/nuxt/ui/compare/v2.16.0...v2.17.0) (2024-06-13)
### Features
* **Alert:** add `actions` slot ([#1785](https://github.com/nuxt/ui/issues/1785)) ([c8dd71c](https://github.com/nuxt/ui/commit/c8dd71c4f5a5239811b07b50f1dc802101af07d5))
* **Form:** update and migrate `valibot` to v0.31.0 ([#1848](https://github.com/nuxt/ui/issues/1848)) ([1d5bd89](https://github.com/nuxt/ui/commit/1d5bd89d5881163fc6dc917e138b9d8304dff6c4))
* **Notification:** allow ring customization with `{color}` ([#1830](https://github.com/nuxt/ui/issues/1830)) ([3ebff4d](https://github.com/nuxt/ui/commit/3ebff4d133372e339e2c4c439576e9e192b29cc3))
* **Slideover:** handle `top` and `bottom` side ([#1834](https://github.com/nuxt/ui/issues/1834)) ([50ad14f](https://github.com/nuxt/ui/commit/50ad14f9dffe4f76bef888cd10d30b417c75bca5))
* **Tabs:** add `content` prop to avoid the render of the HTML markup ([#1831](https://github.com/nuxt/ui/issues/1831)) ([6e2678d](https://github.com/nuxt/ui/commit/6e2678d1d8a498322eb3eff909ccbba55e40a2b7))
### Bug Fixes
* **Alert/Notification:** use `div` for description ([e8898d1](https://github.com/nuxt/ui/commit/e8898d15a667ba66e78828315e3cc4e92845cd3f)), closes [#1551](https://github.com/nuxt/ui/issues/1551)
* **Alert:** base style not applied on icon ([#1859](https://github.com/nuxt/ui/issues/1859)) ([f65aefb](https://github.com/nuxt/ui/commit/f65aefb7067c1c64c1355b5d699129e716ef1281))
* **Breadcrumb:** allow `aria-current` to be overrideable ([ebfb835](https://github.com/nuxt/ui/commit/ebfb8350339725c0a6f88c73f16bff01d61538c2)), closes [#1856](https://github.com/nuxt/ui/issues/1856)
* **Carousel:** prevent mouse click when dragging ([#1781](https://github.com/nuxt/ui/issues/1781)) ([4f0d00f](https://github.com/nuxt/ui/commit/4f0d00f7a6eebf05adceaf1e7c2869ad91949cf3))
* **CommandPalette:** hide `empty-state` when `null` ([249bbd4](https://github.com/nuxt/ui/commit/249bbd49dc8420603e8d561543d237abeb400908)), closes [#1787](https://github.com/nuxt/ui/issues/1787)
* **Form:** maintain other errors when using `setErrors` with a path ([#1818](https://github.com/nuxt/ui/issues/1818)) ([06990be](https://github.com/nuxt/ui/commit/06990beabf67f668322b4d3fb2ec93cc4f3bdcd4))
* **Input:** hide wrapper when type is `hidden` ([#1797](https://github.com/nuxt/ui/issues/1797)) ([e7c2f78](https://github.com/nuxt/ui/commit/e7c2f7856c05ed96f48c83d64d8e1d3f41ab58fe))
* **Link:** typo in `exactHash` type ([581b470](https://github.com/nuxt/ui/commit/581b470cc79c2315bb2d56e02a7c134a7861c616)), closes [#1767](https://github.com/nuxt/ui/issues/1767)
* **SelectMenu:** wrong placeholder color when `modelValue` is an empty string ([9b9ccdb](https://github.com/nuxt/ui/commit/9b9ccdb59e98fed096dd18809af646b10de46b9f)), closes [#1862](https://github.com/nuxt/ui/issues/1862)
* **Select:** remove defaults for `value` and `text` ([6c124bb](https://github.com/nuxt/ui/commit/6c124bb1ac2fef116161da56a3a8e5f92144ce3a)), closes [#1702](https://github.com/nuxt/ui/issues/1702)
## [2.16.0](https://github.com/nuxt/ui/compare/v2.15.2...v2.16.0) (2024-05-07)
### ⚠ BREAKING CHANGES
* **Input:** redesign `file` type without absolute positioning (#1712)
### Features
* **InputMenu/SelectMenu:** allow lazy search ([#1705](https://github.com/nuxt/ui/issues/1705)) ([7e6ba78](https://github.com/nuxt/ui/commit/7e6ba786816516ab5007a2ff15fc974cfdd796ab))
* **module:** HMR support with `@nuxtjs/tailwindcss` ([#1665](https://github.com/nuxt/ui/issues/1665)) ([821e15b](https://github.com/nuxt/ui/commit/821e15b696b03d0f5e20e001d39f86a8b3cec426))
* **Table:** allow providing a `<caption>` ([#1680](https://github.com/nuxt/ui/issues/1680)) ([3fca668](https://github.com/nuxt/ui/commit/3fca66857d3616bf24a1b0579c90179a7883869d))
* **useToast:** allow clearing all notifications ([#1695](https://github.com/nuxt/ui/issues/1695)) ([82d619b](https://github.com/nuxt/ui/commit/82d619b2a75b9d08f3f5b314d37c30d77d8341e9))
### Bug Fixes
* **Breadcrumb:** pass `click` event to `ULink` ([5481dab](https://github.com/nuxt/ui/commit/5481dab53dbe0b28188b4a16811f3e8816d93edf))
* **Input:** redesign `file` type without absolute positioning ([#1712](https://github.com/nuxt/ui/issues/1712)) ([ed5c74d](https://github.com/nuxt/ui/commit/ed5c74dc17df784485eabc39c83e62ada9210a49))
* **Notification:** update timer when timeout prop changes ([#1673](https://github.com/nuxt/ui/issues/1673)) ([cba9ad7](https://github.com/nuxt/ui/commit/cba9ad78db58cb9228bb9c96f0469d43bde2bf3e))
* **Slideover:** export and clean types ([#1692](https://github.com/nuxt/ui/issues/1692)) ([bd3fa86](https://github.com/nuxt/ui/commit/bd3fa8658f84fb7bd96d322968462c5eaa987b86))
* **Table:** provide `aria-sort` for sortable table headings ([#1675](https://github.com/nuxt/ui/issues/1675)) ([6f60fa9](https://github.com/nuxt/ui/commit/6f60fa9a980020f6a5afc2916e699a7f9a47e8ce))
## [2.15.2](https://github.com/nuxt/ui/compare/v2.15.1...v2.15.2) (2024-04-12)
### Features
* **Accordion:** add `unmount` prop to allow lazy mounting for heavy components ([#1590](https://github.com/nuxt/ui/issues/1590)) ([91e5002](https://github.com/nuxt/ui/commit/91e50020507ac66992dfb52b3e0ad1a1ae5614b5))
* **Table:** add `checkbox` ui config ([#1409](https://github.com/nuxt/ui/issues/1409)) ([8b54660](https://github.com/nuxt/ui/commit/8b546600dbfbff187d9c5be1b35ea1772e94f83f))
### Bug Fixes
* **Breadcrumb:** missing `min-w-0` on wrapper to truncate ([9f01145](https://github.com/nuxt/ui/commit/9f01145bc674378371ff34d7110f3235b57d2459)), closes [#1650](https://github.com/nuxt/ui/issues/1650)
* **Carousel:** next and prev buttons disabled ([#1619](https://github.com/nuxt/ui/issues/1619)) ([e909884](https://github.com/nuxt/ui/commit/e909884d0327bfd7b4d5551382123f8998beff6a))
* **Popover/Dropdown:** prevent unintended closure on touchstart in mobile devices ([#1609](https://github.com/nuxt/ui/issues/1609)) ([2392b4a](https://github.com/nuxt/ui/commit/2392b4aa405430fc22766f130448a7cc5ced9a3a))
* **Slideover:** remove dynamic component when closing ([#1615](https://github.com/nuxt/ui/issues/1615)) ([58faa10](https://github.com/nuxt/ui/commit/58faa1053b9be3f627c3fcff1bcaa14850bb9e7f))
* **Slideover:** wait for transition to complete to reset state ([#1624](https://github.com/nuxt/ui/issues/1624)) ([07a4d13](https://github.com/nuxt/ui/commit/07a4d13c0fcb05c87fb42e02a3a2d6c5c52ccf09))
## [2.15.1](https://github.com/nuxt/ui/compare/v2.15.0...v2.15.1) (2024-04-02)
### Features
* **Avatar:** add `as` prop to use `NuxtImg` underneath ([49b73aa](https://github.com/nuxt/ui/commit/49b73aa024be14a9aa150a2804f4dcb18542fa49)), closes [#1577](https://github.com/nuxt/ui/issues/1577)
### Bug Fixes
* **Checkbox:** `[@change](https://github.com/change)` event value ([#1580](https://github.com/nuxt/ui/issues/1580)) ([c98d6e3](https://github.com/nuxt/ui/commit/c98d6e31c0e3f46b97957d5cf3de7f9da1f70c58))
* **Divider:** add `w-full` only on horizontal wrapper ([#1565](https://github.com/nuxt/ui/issues/1565)) ([bd8b737](https://github.com/nuxt/ui/commit/bd8b737642280e6a83b67f9a27dd7a823a77e963))
* **Dropdown:** missing `mouseenter` event on container ([7288953](https://github.com/nuxt/ui/commit/72889535e7e9763e7ebf59498f22c39bf09d6477))
* **Input/SelectMenu:** handle `file` type and `change` events ([#1570](https://github.com/nuxt/ui/issues/1570)) ([878f707](https://github.com/nuxt/ui/commit/878f7078a28c5e70a662682d1293db466d518c7d))
* **Popover:** missing `mouseenter` event on container ([8517897](https://github.com/nuxt/ui/commit/8517897c34adaa9e3624f867b43106deb59fcbe8)), closes [#1564](https://github.com/nuxt/ui/issues/1564)
## [2.15.0](https://github.com/nuxt/ui/compare/v2.14.2...v2.15.0) (2024-03-26)

View File

@@ -1,100 +1,79 @@
[![nuxt-ui.png](https://repository-images.githubusercontent.com/428329515/43fec891-9030-4601-8233-5d45ba5c6013)](https://ui.nuxt.com)
# Nuxt UI
# Nuxt UI v3
[![npm version][npm-version-src]][npm-version-href]
[![npm downloads][npm-downloads-src]][npm-downloads-href]
[![License][license-src]][license-href]
[![Nuxt][nuxt-src]][nuxt-href]
Nuxt UI is a module that provides a set of Vue components and composables built with [Tailwind CSS](https://tailwindcss.com/) and [Headless UI](https://headlessui.dev/) to help you build beautiful and accessible user interfaces.
Its goal is to provide everything related to UI when building a Nuxt app. This includes components, icons, colors, dark mode but also keyboard shortcuts.
## Features
- Built with [Headless UI](https://headlessui.dev/) and [Tailwind CSS](https://tailwindcss.com/)
- HMR support through Nuxt App Config
- Dark mode support
- Support for LTR and RTL languages
- Keyboard shortcuts
- Bundled icons
- Fully typed
- [Figma Kit](https://www.figma.com/community/file/1288455405058138934)
Read more on [ui.nuxt.com](https://ui.nuxt.com)
We're thrilled to introduce Nuxt UI v3, a significant upgrade to our UI library that delivers extensive improvements and robust new capabilities. This major update harnesses the combined strengths of [Radix Vue](https://www.radix-vue.com/), [Tailwind CSS v4](https://tailwindcss.com/blog/tailwindcss-v4-alpha), and [Tailwind Variants](https://www.tailwind-variants.org/) to offer developers an unparalleled set of tools for creating sophisticated, accessible, and highly performant user interfaces.
## Installation
```bash
# npm
npm install @nuxt/ui
# yarn
yarn add @nuxt/ui
# pnpm
pnpm add @nuxt/ui
# bun
bun add @nuxt/ui
1. Install the Nuxt UI v3 alpha package:
```bash [pnpm]
pnpm add @nuxt/ui@next
```
Then, register the module in your `nuxt.config.ts`:
```bash [yarn]
yarn add @nuxt/ui@next
```
```js
```bash [npm]
npm install @nuxt/ui@next
```
```bash [bun]
bun add @nuxt/ui@next
```
> [!WARNING]
> Make sure you have `typescript` installed in your dev dependencies.
2. Register the Nuxt UI module in your `nuxt.config.ts`:
```ts [nuxt.config.ts]
export default defineNuxtConfig({
modules: [
'@nuxt/ui'
]
modules: ['@nuxt/ui']
})
```
If you want latest updates, please use `@nuxt/ui-edge` in your `package.json`:
3. Import Tailwind and Nuxt UI in your `app.vue` or in your [CSS](https://nuxt.com/docs/getting-started/styling#the-css-property):
```json
{
"devDependencies": {
"@nuxt/ui": "npm:@nuxt/ui-edge@latest"
}
}
```css [main.css]
@import "tailwindcss";
@import "@nuxt/ui";
```
## Documentation
Visit https://ui.nuxt.com to explore the documentation.
Visit https://ui3.nuxt.com to explore the documentation.
## Credits
- [nuxt/nuxt](https://github.com/nuxt/nuxt)
- [nuxt/icon](https://github.com/nuxt/icon)
- [nuxt/fonts](https://github.com/nuxt/fonts)
- [nuxt-modules/color-mode](https://github.com/nuxt-modules/color-mode)
- [nuxt-modules/tailwindcss](https://github.com/nuxt-modules/tailwindcss)
- [radix-vue/radix-vue](https://github.com/radix-vue/radix-vue)
- [tailwindlabs/tailwindcss](https://github.com/tailwindlabs/tailwindcss)
- [tailwindlabs/headlessui](https://github.com/tailwindlabs/headlessui)
- [vueuse/vueuse](https://github.com/vueuse/vueuse)
- [egoist/tailwindcss-icons](https://github.com/egoist/tailwindcss-icons)
## Contributing
Thank you for considering contributing to Nuxt UI. Here are a few ways you can get involved:
- Reporting Bugs: If you come across any bugs or issues, please check out the reporting bugs guide to learn how to submit a bug report.
- Suggestions: Have any thoughts to enhance Nuxt UI? We'd love to hear them! Check out the [contribution guide](https://ui.nuxt.com/getting-started/contributing) to share your suggestions.
## Local Development
Follow the docs to [Set up your local development environment](https://ui.nuxt.com/getting-started/contributing#_2-local-development-setup) and contribute.
## License
Licensed under the [MIT license](https://github.com/nuxt/ui/blob/dev/LICENSE.md).
<!-- Badges -->
[npm-version-src]: https://img.shields.io/npm/v/@nuxt/ui/latest.svg?style=flat&colorA=18181B&colorB=28CF8D
[npm-version-src]: https://img.shields.io/npm/v/@nuxt/ui/next.svg?style=flat&colorA=18181B&colorB=28CF8D
[npm-version-href]: https://npmjs.com/package/@nuxt/ui
[npm-downloads-src]: https://img.shields.io/npm/dm/@nuxt/ui.svg?style=flat&colorA=18181B&colorB=28CF8D
[npm-downloads-href]: https://npmjs.com/package/@nuxt/ui
[license-src]: https://img.shields.io/github/license/nuxt/ui.svg?style=flat&colorA=18181B&colorB=28CF8D
[license-href]: https://github.com/nuxt/ui/blob/main/LICENSE
[license-href]: https://github.com/nuxt/ui/blob/main/LICENSE.md
[nuxt-src]: https://img.shields.io/badge/Nuxt-18181B?logo=nuxt.js
[nuxt-href]: https://nuxt.com

12
build.config.ts Normal file
View File

@@ -0,0 +1,12 @@
import { defineBuildConfig } from 'unbuild'
export default defineBuildConfig({
replace: {
'process.env.DEV': 'false'
},
hooks: {
'mkdist:entry:options'(ctx, entry, options) {
options.addRelativeDeclarationExtensions = false
}
}
})

View File

@@ -75,7 +75,7 @@ export default defineCommand({
await sortFile(themePath)
if (!args.prose) {
const typesPath = resolve(path, 'src/runtime/types/index.d.ts')
const typesPath = resolve(path, 'src/runtime/types/index.ts')
await appendFile(typesPath, `export * from '../components/${args.content ? 'content/' : ''}${splitByCase(name).map(p => upperFirst(p)).join('')}.vue'`)
await sortFile(typesPath)
}

View File

@@ -5,7 +5,7 @@ const playground = ({ name, pro }) => {
const kebabName = kebabCase(name)
return {
filename: `playground/pages/components/${kebabName}.vue`,
filename: `playground/app/pages/components/${kebabName}.vue`,
contents: pro
? undefined
: `

View File

@@ -1,8 +1,13 @@
export default defineAppConfig({
toaster: {
position: 'bottom-right' as const,
expand: true,
duration: 5000
},
ui: {
colors: {
primary: 'sky',
gray: 'cool'
primary: 'green',
gray: 'slate'
}
}
})

View File

@@ -1,15 +1,16 @@
<script setup lang="ts">
import { withoutTrailingSlash } from 'ufo'
// import { debounce } from 'perfect-debounce'
import type { ContentSearchFile } from '@nuxt/ui-pro'
// import type { ContentSearchFile } from '@nuxt/ui-pro'
const route = useRoute()
const appConfig = useAppConfig()
// const colorMode = useColorMode()
const runtimeConfig = useRuntimeConfig()
const { integrity, api } = runtimeConfig.public.content
const { data: navigation } = await useAsyncData('navigation', () => fetchContentNavigation(), { default: () => [] })
const { data: files } = await useLazyFetch<ContentSearchFile[]>(`${api.baseURL}/search${integrity ? '.' + integrity : ''}`, { default: () => [] })
const { data: files } = await useLazyFetch<any[]>(`${api.baseURL}/search${integrity ? '-' + integrity : ''}`, { default: () => [] })
const searchTerm = ref('')
@@ -71,23 +72,55 @@ useServerSeoMeta({
})
provide('navigation', navigation)
provide('files', files)
</script>
<template>
<UApp>
<NuxtLoadingIndicator />
<UApp :toaster="appConfig.toaster">
<NuxtLoadingIndicator color="#FFF" />
<Banner v-if="!route.path.startsWith('/examples')" />
<template v-if="!route.path.startsWith('/examples')">
<Banner />
<Header v-if="!route.path.startsWith('/examples')" :links="links" />
<Header :links="links" />
</template>
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
<Footer v-if="!route.path.startsWith('/examples')" />
<template v-if="!route.path.startsWith('/examples')">
<Footer />
<LazyUContentSearch v-model:search-term="searchTerm" :files="files" :navigation="navigation" :fuse="{ resultLimit: 42 }" />
<ClientOnly>
<LazyUContentSearch v-model:search-term="searchTerm" :files="files" :navigation="navigation" :fuse="{ resultLimit: 42 }" />
</ClientOnly>
</template>
</UApp>
</template>
<style>
@import "tailwindcss";
@import "@nuxt/ui-pro";
@source "../content/**/*.md";
@theme {
--font-family-sans: 'Public Sans', sans-serif;
--color-green-50: #EFFDF5;
--color-green-100: #D9FBE8;
--color-green-200: #B3F5D1;
--color-green-300: #75EDAE;
--color-green-400: #00DC82;
--color-green-500: #00C16A;
--color-green-600: #00A155;
--color-green-700: #007F45;
--color-green-800: #016538;
--color-green-900: #0A5331;
--color-green-950: #052E16;
}
:root {
--container-width: 90rem;
}
</style>

View File

@@ -1,5 +1,5 @@
<template>
<UBanner icon="i-heroicons-light-bulb" :actions="[{ label: 'Go to Nuxt UI v2', to: 'https://ui.nuxt.com', trailingIcon: 'i-heroicons-arrow-right-20-solid', class: 'rounded-full' }]" :close="false">
<UBanner icon="i-heroicons-wrench-screwdriver" :actions="[{ label: 'Go to Nuxt UI v2', to: 'https://ui.nuxt.com', trailingIcon: 'i-heroicons-arrow-right-20-solid', class: 'rounded-full' }]" :close="false">
<template #title>
You're looking at the documentation for <span class="font-semibold">Nuxt UI v3</span>!
</template>

View File

@@ -2,7 +2,7 @@
import type { NavItem } from '@nuxt/content'
import type { NavigationMenuItem } from '@nuxt/ui'
const props = defineProps<{
defineProps<{
links: NavigationMenuItem[]
}>()
@@ -10,16 +10,16 @@ const config = useRuntimeConfig().public
const navigation = inject<Ref<NavItem[]>>('navigation')
const items = computed(() => props.links.map(({ icon, ...link }) => link))
// const items = computed(() => props.links.map(({ icon, ...link }) => link))
</script>
<template>
<UHeader>
<UHeader :ui="{ left: 'min-w-0' }">
<template #left>
<NuxtLink to="/" class="flex items-end gap-2 font-bold text-xl text-gray-900 dark:text-white" aria-label="Nuxt UI">
<Logo class="w-auto h-6" />
<NuxtLink to="/" class="flex items-end gap-2 font-bold text-xl text-gray-900 dark:text-white min-w-0" aria-label="Nuxt UI">
<Logo class="w-auto h-6 shrink-0" />
<UBadge :label="`v${config.version}`" variant="subtle" size="sm" class="-mb-[2px] rounded font-semibold" />
<UBadge :label="`v${config.version}`" variant="subtle" size="sm" class="-mb-[2px] rounded font-semibold truncate" />
</NuxtLink>
</template>
@@ -45,9 +45,9 @@ const items = computed(() => props.links.map(({ icon, ...link }) => link))
</template>
<template #content>
<UNavigationMenu orientation="vertical" :items="items" class="-ml-2.5" />
<!-- <UNavigationMenu orientation="vertical" :items="items" class="-ml-2.5" />
<USeparator type="dashed" class="my-4" />
<USeparator type="dashed" class="my-4" /> -->
<UContentNavigation :navigation="navigation" />
</template>

View File

@@ -2,10 +2,13 @@
<script setup lang="ts">
import json5 from 'json5'
import { upperFirst, camelCase, kebabCase } from 'scule'
import { hash } from 'ohash'
import * as theme from '#build/ui'
import { get, set } from '#ui/utils'
const props = defineProps<{
/** Override the slug taken from the route */
slug?: string
class?: any
/** List of props to ignore in selection */
ignore?: string[]
@@ -13,6 +16,8 @@ const props = defineProps<{
hide?: string[]
/** List of props to externalize in script setup */
external?: string[]
/** List of props to use with `v-model` */
model?: string[]
/** List of items for each prop */
items?: { [key: string]: string[] }
props?: { [key: string]: any }
@@ -32,24 +37,28 @@ const props = defineProps<{
const route = useRoute()
const { $prettier } = useNuxtApp()
const camelName = camelCase(route.params.slug[route.params.slug.length - 1])
const camelName = camelCase(props.slug ?? route.params.slug?.[route.params.slug.length - 1] ?? '')
const name = `U${upperFirst(camelName)}`
const componentProps = reactive({ ...(props.props || {}) })
const componentEvents = reactive({
...Object.fromEntries((props.model || []).map(key => [`onUpdate:${key}`, (e: any) => setComponentProp(key, e)])),
...(componentProps.modelValue ? { [`onUpdate:modelValue`]: (e: any) => setComponentProp('modelValue', e) } : {})
})
function getComponentProp(name: string) {
return get(componentProps, name) || undefined
return get(componentProps, name) ?? undefined
}
function setComponentProp(name: string, value: any) {
set(componentProps, name, value)
}
const componentTheme = theme[camelName]
const componentTheme = (theme as any)[camelName]
const meta = await fetchComponentMeta(name as any)
function mapKeys(obj, parentKey = '') {
return Object.entries(obj || {}).flatMap(([key, value]) => {
function mapKeys(obj: object, parentKey = ''): any {
return Object.entries(obj || {}).flatMap(([key, value]: [string, any]) => {
if (typeof value === 'object' && !Array.isArray(value)) {
return mapKeys(value, key)
}
@@ -63,15 +72,15 @@ function mapKeys(obj, parentKey = '') {
const options = computed(() => {
const keys = mapKeys(props.props || {})
return keys.map((key) => {
return keys.map((key: string) => {
const prop = meta?.meta?.props?.find((prop: any) => prop.name === key)
const propItems = get(props.items, key, [])
const items = propItems.length
? propItems.map(item => ({
? propItems.map((item: any) => ({
value: item,
label: item
}))
: prop?.type === 'boolean'
: prop?.type === 'boolean' || prop?.type === 'boolean | undefined'
? [{ value: true, label: 'true' }, { value: false, label: 'false' }]
: Object.keys(componentTheme?.variants?.[key] || {}).map(variant => ({
value: variant,
@@ -119,6 +128,11 @@ const code = computed(() => {
continue
}
if (props.model?.includes(key)) {
code += ` v-model:${key}="${key}"`
continue
}
if (value === undefined || value === null || value === '' || props.hide?.includes(key)) {
continue
}
@@ -177,7 +191,7 @@ const code = computed(() => {
return code
})
const { data: ast } = await useAsyncData(`component-code-${name}-${JSON.stringify({ props: componentProps, slots: props.slots })}`, async () => {
const { data: ast } = await useAsyncData(`component-code-${name}-${hash({ props: componentProps, slots: props.slots })}`, async () => {
if (!props.prettier) {
return parseMarkdown(code.value)
}
@@ -200,7 +214,7 @@ const { data: ast } = await useAsyncData(`component-code-${name}-${JSON.stringif
<template>
<div class="my-5">
<div>
<div v-if="options.length" class="flex items-center gap-2.5 border border-gray-300 dark:border-gray-700 border-b-0 relative rounded-t-md px-4 py-2.5 overflow-x-auto">
<div v-if="options.length" class="flex items-center gap-2.5 border border-gray-200 dark:border-gray-700 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"
@@ -220,7 +234,7 @@ const { data: ast } = await useAsyncData(`component-code-${name}-${JSON.stringif
color="gray"
variant="soft"
class="rounded rounded-l-none min-w-12"
:search="false"
:search-input="false"
:class="[option.name.toLowerCase().endsWith('color') && 'pl-6']"
:ui="{ itemLeadingChip: 'size-2' }"
@update:model-value="setComponentProp(option.name, $event)"
@@ -249,11 +263,11 @@ const { data: ast } = await useAsyncData(`component-code-${name}-${JSON.stringif
</template>
</div>
<div class="flex border border-b-0 border-gray-300 dark:border-gray-700 relative p-4 z-[1]" :class="[!options.length && 'rounded-t-md', props.class]">
<component :is="name" v-bind="componentProps" @update:model-value="!!componentProps.modelValue && setComponentProp('modelValue', $event)">
<div class="flex justify-center border border-b-0 border-gray-200 dark:border-gray-700 relative p-4 z-[1]" :class="[!options.length && 'rounded-t-md', props.class]">
<component :is="name" v-bind="{ ...componentProps, ...componentEvents }">
<template v-for="slot in Object.keys(slots || {})" :key="slot" #[slot]>
<ContentSlot :name="slot" unwrap="p">
{{ slots[slot] }}
{{ slots?.[slot] }}
</ContentSlot>
</template>
</component>

View File

@@ -3,7 +3,7 @@ import { upperFirst, camelCase } from 'scule'
const route = useRoute()
const camelName = camelCase(route.params.slug[route.params.slug.length - 1])
const camelName = camelCase(route.params.slug?.[route.params.slug.length - 1] ?? '')
const name = `U${upperFirst(camelName)}`
const meta = await fetchComponentMeta(name as any)
@@ -22,7 +22,7 @@ const meta = await fetchComponentMeta(name as any)
</ProseTr>
</ProseThead>
<ProseTbody>
<ProseTr v-for="event in meta.meta.events" :key="event.name">
<ProseTr v-for="event in (meta?.meta?.events || [])" :key="event.name">
<ProseTd>
<ProseCodeInline>
{{ event.name }}

View File

@@ -1,5 +1,6 @@
<script setup lang="ts">
import { camelCase } from 'scule'
import { get, set } from '#ui/utils'
const props = withDefaults(defineProps<{
name: string
@@ -21,10 +22,33 @@ const props = withDefaults(defineProps<{
* @defaultValue true
*/
preview?: boolean
/**
* Whether to show the source code
* @defaultValue true
*/
source?: boolean
/**
* A list of variable props to link to the component.
*/
options?: Array<{
alias?: string
name: string
label: string
items?: any[]
default: any
multiple?: boolean
}>
}>(), {
preview: true
preview: true,
source: true
})
const slots = defineSlots<{
options(props?: {}): any
}>()
const { $prettier } = useNuxtApp()
const camelName = camelCase(props.name)
@@ -71,16 +95,83 @@ const { data: ast } = await useAsyncData(`component-example-${camelName}`, async
return parseMarkdown(formatted)
}, { watch: [code] })
const optionsValues = ref(props.options?.reduce((acc, option) => {
if (option.name) {
acc[option.alias || option.name] = option.default
}
if (option.name.toLowerCase().endsWith('color') && option.items?.length) {
option.items = option.items.map((item: any) => ({
label: item,
value: item,
chip: { color: item }
}))
}
return acc
}, {} as Record<string, any>) || {})
</script>
<template>
<div class="my-5">
<div v-if="preview">
<div class="flex border border-b-0 border-gray-300 dark:border-gray-700 relative p-4 rounded-t-md" :class="[props.class]">
<component :is="camelName" v-bind="componentProps" />
<div class="border border-gray-200 dark:border-gray-700 relative z-[1]" :class="[{ 'border-b-0 rounded-t-md': props.source, 'rounded-md': !props.source }]">
<div v-if="props.options?.length || !!slots.options" class="flex gap-4 p-4 border-b border-gray-200 dark:border-gray-700">
<slot name="options" />
<UFormField
v-for="option in props.options"
:key="option.name"
:label="option.label"
:name="option.name"
size="sm"
class="inline-flex ring ring-gray-300 dark:ring-gray-700 rounded"
:ui="{
wrapper: 'bg-gray-50 dark:bg-gray-800/50 rounded-l flex border-r border-gray-300 dark:border-gray-700',
label: 'text-gray-500 dark:text-gray-400 px-2 py-1.5',
container: 'mt-0'
}"
>
<USelectMenu
v-if="option.items?.length"
:model-value="get(optionsValues, option.name)"
:items="option.items"
:search-input="false"
:value-key="option.name.toLowerCase().endsWith('color') ? 'value' : undefined"
color="gray"
variant="soft"
class="rounded rounded-l-none min-w-12"
:multiple="option.multiple"
:class="[option.name.toLowerCase().endsWith('color') && 'pl-6']"
:ui="{ itemLeadingChip: 'size-2' }"
@update:model-value="set(optionsValues, option.name, $event)"
>
<template v-if="option.name.toLowerCase().endsWith('color')" #leading="{ modelValue, ui }">
<UChip
inset
standalone
:color="(modelValue as any)"
:size="ui.itemLeadingChipSize()"
class="size-2"
/>
</template>
</USelectMenu>
<UInput
v-else
:model-value="get(optionsValues, option.name)"
color="gray"
variant="soft"
:ui="{ base: 'rounded rounded-l-none min-w-12' }"
@update:model-value="set(optionsValues, option.name, $event)"
/>
</UFormField>
</div>
<div class="flex justify-center p-4" :class="props.class">
<component :is="camelName" v-bind="{ ...componentProps, ...optionsValues }" />
</div>
</div>
</div>
<MDCRenderer v-if="ast" :body="ast.body" :data="ast.data" class="[&_pre]:!rounded-t-none [&_div.my-5]:!mt-0" />
<MDCRenderer v-if="ast && props.source" :body="ast.body" :data="ast.data" class="[&_pre]:!rounded-t-none [&_div.my-5]:!mt-0" />
</div>
</template>

View File

@@ -9,10 +9,10 @@ const props = defineProps<{
const route = useRoute()
const camelName = camelCase(route.params.slug[route.params.slug.length - 1])
const camelName = camelCase(route.params.slug?.[route.params.slug.length - 1] ?? '')
const name = `U${upperFirst(camelName)}`
const componentTheme = theme[camelName]
const componentTheme = (theme as any)[camelName]
const meta = await fetchComponentMeta(name as any)
const metaProps: ComputedRef<ComponentMeta['props']> = computed(() => {
@@ -43,6 +43,8 @@ const metaProps: ComputedRef<ComponentMeta['props']> = computed(() => {
if (b.name === 'ui') {
return -1
}
return 0
})
})
</script>

View File

@@ -6,7 +6,7 @@ const props = defineProps<{
ignore?: string[]
}>()
function getSchemaProps(schema: PropertyMeta['schema']) {
function getSchemaProps(schema: PropertyMeta['schema']): any {
if (!schema || typeof schema === 'string' || !schema.schema) {
return []
}
@@ -15,12 +15,12 @@ function getSchemaProps(schema: PropertyMeta['schema']) {
return Object.values(schema.schema).filter(prop => !props.ignore?.includes(prop.name))
}
return (Array.isArray(schema.schema) ? schema.schema : Object.values(schema.schema)).flatMap(getSchemaProps)
return (Array.isArray(schema.schema) ? schema.schema : Object.values(schema.schema)).flatMap(getSchemaProps as any)
}
const schemaProps = computed(() => {
return getSchemaProps(props.prop.schema).map((prop) => {
const defaultValue = prop.default ?? prop.tags?.find(tag => tag.name === 'defaultValue')?.text
return getSchemaProps(props.prop.schema).map((prop: any) => {
const defaultValue = prop.default ?? prop.tags?.find((tag: any) => tag.name === 'defaultValue')?.text
let description = prop.description
if (defaultValue) {
description = description ? `${description} Defaults to \`${defaultValue}\`{lang="ts-type"}.` : `Defaults to \`${defaultValue}\`{lang="ts-type"}.`

View File

@@ -3,7 +3,7 @@ import { upperFirst, camelCase } from 'scule'
const route = useRoute()
const camelName = camelCase(route.params.slug[route.params.slug.length - 1])
const camelName = camelCase(route.params.slug?.[route.params.slug.length - 1] ?? '')
const name = `U${upperFirst(camelName)}`
const meta = await fetchComponentMeta(name as any)
@@ -22,7 +22,7 @@ const meta = await fetchComponentMeta(name as any)
</ProseTr>
</ProseThead>
<ProseTbody>
<ProseTr v-for="slot in meta.meta.slots" :key="slot.name">
<ProseTr v-for="slot in (meta?.meta?.slots || [])" :key="slot.name">
<ProseTd>
<ProseCodeInline>
{{ slot.name }}

View File

@@ -5,12 +5,12 @@ import * as theme from '#build/ui'
const route = useRoute()
const name = camelCase(route.params.slug[route.params.slug.length - 1])
const name = camelCase(route.params.slug?.[route.params.slug.length - 1] ?? '')
const strippedCompoundVariants = ref(false)
function stripCompoundVariants(component) {
if (component.compoundVariants) {
function stripCompoundVariants(component?: any) {
if (component?.compoundVariants) {
component.compoundVariants = component.compoundVariants.filter((compoundVariant: any) => {
if (compoundVariant.color) {
if (!['primary', 'gray'].includes(compoundVariant.color)) {
@@ -38,7 +38,7 @@ function stripCompoundVariants(component) {
const component = computed(() => {
return {
ui: {
[name]: stripCompoundVariants(theme[name])
[name]: stripCompoundVariants((theme as any)[name])
}
}
})
@@ -54,7 +54,7 @@ export default defineAppConfig(${json5.stringify(component.value, null, 2).repla
${strippedCompoundVariants.value
? `
::callout{icon="i-simple-icons-github" to="https://github.com/benjamincanac/ui3/blob/dev/src/theme/${name}.ts"}
::callout{icon="i-simple-icons-github" to="https://github.com/nuxt/ui/blob/v3/src/theme/${name}.ts"}
Some colors in \`compoundVariants\` are omitted for readability. Check out the source code on GitHub.
::`
: ''}

View File

@@ -1,20 +1,29 @@
<script setup lang="ts">
import { murmurHash } from 'ohash'
const props = defineProps<{
type: string
}>()
const type = computed(() => {
if (props.type.includes(', "as" | "asChild" | "forceMount">')) {
return props.type.replace(`, "as" | "asChild" | "forceMount">`, ``).replace('Omit<', '')
let type = props.type
if (type.includes(', "as" | "asChild" | "forceMount">')) {
type = type.replace(`, "as" | "asChild" | "forceMount">`, ``).replace('Omit<', '')
}
if (props.type.includes(', "as" | "asChild">')) {
return props.type.replace(', "as" | "asChild">', '').replace('Omit<', '')
if (type.includes(', "as" | "asChild">')) {
type = type.replace(', "as" | "asChild">', '').replace('Omit<', '')
}
if (type.startsWith('undefined |')) {
type = type.replace('undefined |', '')
}
if (type.endsWith('| undefined')) {
type = type.replace('| undefined', '')
}
return props.type
return type
})
const { data: ast } = await useAsyncData(`hightlight-inline-code-${props.type}`, () => parseMarkdown(`\`${type.value}\`{lang="ts-type"}`))
const { data: ast } = await useAsyncData(`hightlight-inline-code-${murmurHash(type.value)}`, () => parseMarkdown(`\`${type.value}\`{lang="ts-type"}`))
</script>
<template>

View File

@@ -1,32 +1,19 @@
<script setup lang="ts">
import json5 from 'json5'
const appConfig = useAppConfig()
const { $prettier } = useNuxtApp()
import icons from '../../../../src/theme/icons'
const { data: ast } = await useAsyncData(`icons-theme`, async () => {
const md = `
\`\`\`ts [app.config.ts]
export default defineAppConfig({
ui: {
icons: ${json5.stringify(appConfig.ui.icons, null, 2)}
}
})
export default defineAppConfig(${json5.stringify({
ui: {
icons
}
}, null, 2).replace(/,([ |\t\n]+[}|\])])/g, '$1')})
\`\`\`\
`
let formatted = ''
try {
formatted = await $prettier.format(md, {
trailingComma: 'none',
semi: false,
singleQuote: true
})
} catch {
formatted = md
}
return parseMarkdown(formatted)
return parseMarkdown(md)
})
</script>

View File

@@ -0,0 +1,11 @@
<script setup lang="ts">
async function onClick() {
return new Promise<void>(res => setTimeout(res, 1000))
}
</script>
<template>
<UButton loading-auto @click="onClick">
Button
</UButton>
</template>

View File

@@ -0,0 +1,23 @@
<script setup lang="ts">
const state = reactive({ fullName: '' })
async function onSubmit() {
return new Promise<void>(res => setTimeout(res, 1000))
}
async function validate(data: Partial<typeof state>) {
if (!data.fullName?.length) return [{ name: 'fullName', message: 'Required' }]
return []
}
</script>
<template>
<UForm :state="state" :validate="validate" @submit="onSubmit">
<UFormField name="fullName" label="Full name">
<UInput v-model="state.fullName" />
</UFormField>
<UButton type="submit" class="mt-2" loading-auto>
Submit
</UButton>
</UForm>
</template>

View File

@@ -2,19 +2,16 @@
const statuses = ['online', 'away', 'busy', 'offline']
const status = ref(statuses[0])
const color = computed(() => ({
online: 'green',
away: 'amber',
busy: 'red',
offline: 'gray'
})[status.value] as any)
const color = computed(() => status.value ? { online: 'green', away: 'amber', busy: 'red', offline: 'gray' }[status.value] as any : 'online')
const show = computed(() => status.value !== 'offline')
// Note: This is for demonstration purposes only. Don't do this at home.
onMounted(() => {
setInterval(() => {
status.value = statuses[(statuses.indexOf(status.value) + 1) % statuses.length]
if (status.value) {
status.value = statuses[(statuses.indexOf(status.value) + 1) % statuses.length]
}
}, 1000)
})
</script>

View File

@@ -0,0 +1,48 @@
<script setup lang="ts">
const groups = [{
id: 'settings',
items: [{
label: 'Profile',
icon: 'i-heroicons-user',
kbds: ['meta', 'P']
}, {
label: 'Billing',
icon: 'i-heroicons-credit-card',
kbds: ['meta', 'B'],
slot: 'billing'
}, {
label: 'Notifications',
icon: 'i-heroicons-bell'
}, {
label: 'Security',
icon: 'i-heroicons-lock-closed'
}]
}, {
id: 'users',
label: 'Users',
slot: 'users',
items: [
{ id: 1, label: 'Durward Reynolds' },
{ id: 2, label: 'Kenton Towne' },
{ id: 3, label: 'Therese Wunsch' },
{ id: 4, label: 'Benedict Kessler' },
{ id: 5, label: 'Katelyn Rohan' }
]
}]
</script>
<template>
<UCommandPalette :groups="groups" class="flex-1 h-80">
<template #users-leading="{ index }">
<UAvatar :src="`https://i.pravatar.cc/120?img=${index}`" size="2xs" />
</template>
<template #billing-label="{ item }">
{{ item.label }}
<UBadge variant="subtle" size="sm">
50% off
</UBadge>
</template>
</UCommandPalette>
</template>

View File

@@ -0,0 +1,25 @@
<script setup lang="ts">
const searchTerm = ref('')
const { data: users, status } = await useFetch('https://jsonplaceholder.typicode.com/users', {
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}` } })) || []
},
lazy: true
})
const groups = computed(() => [{
id: 'users',
label: searchTerm.value ? `Users matching “${searchTerm.value}”...` : 'Users',
items: users.value || []
}])
</script>
<template>
<UCommandPalette
v-model:search-term="searchTerm"
:loading="status === 'pending'"
:groups="groups"
class="flex-1 h-80"
/>
</template>

View File

@@ -0,0 +1,28 @@
<script setup lang="ts">
const searchTerm = ref('')
const searchTermDebounced = refDebounced(searchTerm, 200)
const { data: users, status } = await useFetch('https://jsonplaceholder.typicode.com/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}` } })) || []
},
lazy: true
})
const groups = computed(() => [{
id: 'users',
label: searchTerm.value ? `Users matching “${searchTerm.value}”...` : 'Users',
items: users.value || [],
filter: false
}])
</script>
<template>
<UCommandPalette
v-model:search-term="searchTerm"
:loading="status === 'pending'"
:groups="groups"
class="flex-1 h-80"
/>
</template>

View File

@@ -0,0 +1,16 @@
<script setup lang="ts">
const { data: users } = await useFetch('https://jsonplaceholder.typicode.com/users', {
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}` } })) || []
},
lazy: true
})
</script>
<template>
<UCommandPalette
:groups="[{ id: 'users', items: users || [] }]"
:fuse="{ fuseOptions: { includeMatches: true } }"
class="flex-1 h-80"
/>
</template>

View File

@@ -0,0 +1,24 @@
<script setup lang="ts">
const users = [
{ id: 1, label: 'Durward Reynolds' },
{ id: 2, label: 'Kenton Towne' },
{ id: 3, label: 'Therese Wunsch' },
{ id: 4, label: 'Benedict Kessler' },
{ id: 5, label: 'Katelyn Rohan' }
]
const selected = ref(users[0])
function onSelect(item: any) {
console.log(item)
}
</script>
<template>
<UCommandPalette
v-model="selected"
:groups="[{ id: 'users', items: users }]"
class="flex-1"
@update:model-value="onSelect"
/>
</template>

View File

@@ -0,0 +1,25 @@
<script setup lang="ts">
const users = [
{ id: 1, label: 'Durward Reynolds' },
{ id: 2, label: 'Kenton Towne' },
{ id: 3, label: 'Therese Wunsch' },
{ id: 4, label: 'Benedict Kessler' },
{ id: 5, label: 'Katelyn Rohan' }
]
const selected = ref([users[0], users[1]])
function onSelect(items: any) {
console.log(items)
}
</script>
<template>
<UCommandPalette
v-model="selected"
multiple
:groups="[{ id: 'users', items: users }]"
class="flex-1"
@update:model-value="onSelect"
/>
</template>

View File

@@ -0,0 +1,30 @@
<script setup lang="ts">
const open = ref(false)
const users = [
{ id: 1, label: 'Durward Reynolds' },
{ id: 2, label: 'Kenton Towne' },
{ id: 3, label: 'Therese Wunsch' },
{ id: 4, label: 'Benedict Kessler' },
{ id: 5, label: 'Katelyn Rohan' }
]
</script>
<template>
<UModal v-model:open="open">
<UButton
label="Search users..."
color="gray"
variant="subtle"
icon="i-heroicons-magnifying-glass"
/>
<template #content>
<UCommandPalette
close
:groups="[{ id: 'users', items: users }]"
@update:open="open = $event"
/>
</template>
</UModal>
</template>

View File

@@ -0,0 +1,40 @@
<script setup lang="ts">
const items = [{
id: '/',
label: 'Introduction',
level: 1
}, {
id: '/getting-started#whats-new-in-v3',
label: 'What\'s new in v3?',
level: 2
}, {
id: '/getting-started#radix-vue-3',
label: 'Radix Vue',
level: 3
}, {
id: '/getting-started#tailwind-css-v4',
label: 'Tailwind CSS v4',
level: 3
}, {
id: '/getting-started#tailwind-variants',
label: 'Tailwind Variants',
level: 3
}, {
id: '/getting-started/installation',
label: 'Installation',
level: 1
}]
function postFilter(searchTerm: string, items: any[]) {
// Filter only first level items if no searchTerm
if (!searchTerm) {
return items?.filter(item => item.level === 1)
}
return items
}
</script>
<template>
<UCommandPalette :groups="[{ id: 'files', items, postFilter }]" class="flex-1" />
</template>

View File

@@ -0,0 +1,19 @@
<script setup lang="ts">
const users = [
{ id: 1, label: 'Durward Reynolds' },
{ id: 2, label: 'Kenton Towne' },
{ id: 3, label: 'Therese Wunsch' },
{ id: 4, label: 'Benedict Kessler' },
{ id: 5, label: 'Katelyn Rohan' }
]
const searchTerm = ref('Th')
</script>
<template>
<UCommandPalette
v-model:search-term="searchTerm"
:groups="[{ id: 'users', items: users }]"
class="flex-1"
/>
</template>

View File

@@ -3,7 +3,7 @@ const searchTerm = ref('')
const { data: users, status } = await useFetch('https://jsonplaceholder.typicode.com/users', {
params: { q: searchTerm },
transform: (data: any[]) => {
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}` } })) || []
},
lazy: true
@@ -19,7 +19,12 @@ const groups = computed(() => [{
<template>
<UDrawer>
<UButton label="Search users..." color="gray" variant="subtle" icon="i-heroicons-magnifying-glass" />
<UButton
label="Search users..."
color="gray"
variant="subtle"
icon="i-heroicons-magnifying-glass"
/>
<template #content>
<UCommandPalette

View File

@@ -3,7 +3,7 @@ const open = ref(false)
</script>
<template>
<UDrawer v-model:open="open" title="Drawer with footer" description="This is useful when you want a form in a Drawer.">
<UDrawer v-model:open="open" title="Drawer with footer" description="This is useful when you want a form in a Drawer." :ui="{ container: 'max-w-xl mx-auto' }">
<UButton label="Open" color="gray" variant="subtle" trailing-icon="i-heroicons-chevron-up-20-solid" />
<template #body>

View File

@@ -0,0 +1,37 @@
<script setup lang="ts">
import type { FormError, FormSubmitEvent } from '#ui/types'
const state = reactive({
email: undefined,
password: undefined
})
const validate = (state: any): FormError[] => {
const errors = []
if (!state.email) errors.push({ name: 'email', message: 'Required' })
if (!state.password) errors.push({ name: 'password', message: 'Required' })
return errors
}
const toast = useToast()
async function onSubmit(event: FormSubmitEvent<any>) {
toast.add({ title: 'Success', description: 'The form has been submitted.', color: 'green' })
console.log(event.data)
}
</script>
<template>
<UForm :validate="validate" :state="state" class="space-y-4" @submit="onSubmit">
<UFormField label="Email" name="email">
<UInput v-model="state.email" />
</UFormField>
<UFormField label="Password" name="password">
<UInput v-model="state.password" type="password" />
</UFormField>
<UButton type="submit">
Submit
</UButton>
</UForm>
</template>

View File

@@ -0,0 +1,115 @@
<script setup lang="ts">
import { z } from 'zod'
import type { FormSubmitEvent, Form } from '@nuxt/ui'
const schema = z.object({
input: z.string().min(10),
inputMenu: z.any().refine(option => option?.value === 'option-2', {
message: 'Select Option 2'
}),
inputMenuMultiple: z.any().refine(values => !!values?.find((option: any) => option.value === 'option-2'), {
message: 'Include Option 2'
}),
textarea: z.string().min(10),
select: z.string().refine(value => value === 'option-2', {
message: 'Select Option 2'
}),
selectMenu: z.any().refine(option => option?.value === 'option-2', {
message: 'Select Option 2'
}),
selectMenuMultiple: z.any().refine(values => !!values?.find((option: any) => option.value === 'option-2'), {
message: 'Include Option 2'
}),
switch: z.boolean().refine(value => value === true, {
message: 'Toggle me'
}),
checkbox: z.boolean().refine(value => value === true, {
message: 'Check me'
}),
radioGroup: z.string().refine(value => value === 'option-2', {
message: 'Select Option 2'
}),
slider: z.number().max(20, { message: 'Must be less than 20' })
})
type Schema = z.output<typeof schema>
const state = reactive<Partial<Schema>>({})
const form = ref<Form<Schema>>()
const items = [
{ label: 'Option 1', value: 'option-1' },
{ label: 'Option 2', value: 'option-2' },
{ label: 'Option 3', value: 'option-3' }
]
const toast = useToast()
async function onSubmit(event: FormSubmitEvent<any>) {
toast.add({ title: 'Success', description: 'The form has been submitted.', color: 'green' })
console.log(event.data)
}
</script>
<template>
<UForm ref="form" :state="state" :schema="schema" @submit="onSubmit">
<div class="grid grid-cols-3 gap-4">
<UFormField label="Input" name="input">
<UInput v-model="state.input" placeholder="john@lennon.com" class="w-40" />
</UFormField>
<div class="flex flex-col gap-4">
<UFormField name="switch">
<USwitch v-model="state.switch" label="Switch me" />
</UFormField>
<UFormField name="checkbox">
<UCheckbox v-model="state.checkbox" label="Check me" />
</UFormField>
</div>
<UFormField name="slider" label="Slider">
<USlider v-model="state.slider" />
</UFormField>
<UFormField name="select" label="Select">
<USelect v-model="state.select" :items="items" />
</UFormField>
<UFormField name="selectMenu" label="Select Menu">
<USelectMenu v-model="state.selectMenu" :items="items" />
</UFormField>
<UFormField name="selectMenuMultiple" label="Select Menu (Multiple)">
<USelectMenu v-model="state.selectMenuMultiple" multiple :items="items" />
</UFormField>
<UFormField name="inputMenu" label="Input Menu">
<UInputMenu v-model="state.inputMenu" :items="items" />
</UFormField>
<UFormField name="inputMenuMultiple" label="Input Menu (Multiple)">
<UInputMenu v-model="state.inputMenuMultiple" multiple :items="items" />
</UFormField>
<span />
<UFormField label="Textarea" name="textarea">
<UTextarea v-model="state.textarea" />
</UFormField>
<UFormField name="radioGroup">
<URadioGroup v-model="state.radioGroup" legend="Radio group" :items="items" />
</UFormField>
</div>
<div class="flex gap-2 mt-8">
<UButton color="gray" type="submit">
Submit
</UButton>
<UButton color="gray" variant="outline" @click="form?.clear()">
Clear
</UButton>
</div>
</UForm>
</template>

View File

@@ -0,0 +1,38 @@
<script setup lang="ts">
import Joi from 'joi'
import type { FormSubmitEvent } from '#ui/types'
const schema = Joi.object({
email: Joi.string().required(),
password: Joi.string()
.min(8)
.required()
})
const state = reactive({
email: undefined,
password: undefined
})
const toast = useToast()
async function onSubmit(event: FormSubmitEvent<any>) {
toast.add({ title: 'Success', description: 'The form has been submitted.', color: 'green' })
console.log(event.data)
}
</script>
<template>
<UForm :schema="schema" :state="state" class="space-y-4" @submit="onSubmit">
<UFormField label="Email" name="email">
<UInput v-model="state.email" />
</UFormField>
<UFormField label="Password" name="password">
<UInput v-model="state.password" type="password" />
</UFormField>
<UButton type="submit">
Submit
</UButton>
</UForm>
</template>

View File

@@ -0,0 +1,54 @@
<script setup lang="ts">
import { z } from 'zod'
import type { FormSubmitEvent } from '@nuxt/ui'
const schema = z.object({
name: z.string().min(2),
news: z.boolean()
})
type Schema = z.output<typeof schema>
const nestedSchema = z.object({
email: z.string().email()
})
type NestedSchema = z.output<typeof nestedSchema>
const state = reactive<Partial<Schema & NestedSchema>>({ })
const toast = useToast()
async function onSubmit(event: FormSubmitEvent<any>) {
toast.add({ title: 'Success', description: 'The form has been submitted.', color: 'green' })
console.log(event.data)
}
</script>
<template>
<UForm
:state="state"
:schema="schema"
class="gap-4 flex flex-col w-60"
@submit="onSubmit"
>
<UFormField label="Name" name="name">
<UInput v-model="state.name" placeholder="John Lennon" />
</UFormField>
<div>
<UCheckbox v-model="state.news" name="news" label="Register to our newsletter" />
</div>
<UForm v-if="state.news" :state="state" :schema="nestedSchema">
<UFormField label="Email" name="email">
<UInput v-model="state.email" placeholder="john@lennon.com" />
</UFormField>
</UForm>
<div>
<UButton type="submit">
Submit
</UButton>
</div>
</UForm>
</template>

View File

@@ -0,0 +1,79 @@
<script setup lang="ts">
import { z } from 'zod'
import type { FormSubmitEvent } from '@nuxt/ui'
const schema = z.object({
customer: z.string().min(2)
})
type Schema = z.output<typeof schema>
const itemSchema = z.object({
description: z.string().min(1),
price: z.number().min(0)
})
type ItemSchema = z.output<typeof itemSchema>
const state = reactive<Partial<Schema & { items: Partial<ItemSchema>[] }>>({
items: [{}]
})
function addItem() {
if (!state.items) {
state.items = []
}
state.items.push({})
}
function removeItem() {
if (state.items) {
state.items.pop()
}
}
const formItemRef = ref()
const toast = useToast()
async function onSubmit(event: FormSubmitEvent<any>) {
toast.add({ title: 'Success', description: 'The form has been submitted.', color: 'green' })
console.log(event.data)
}
</script>
<template>
<UForm
ref="formItemRef"
:state="state"
:schema="schema"
class="gap-4 flex flex-col w-60"
@submit="onSubmit"
>
<UFormField label="Customer" name="customer">
<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">
<UFormField :label="!count ? 'Description' : undefined" name="description">
<UInput v-model="item.description" />
</UFormField>
<UFormField :label="!count ? 'Price' : undefined" name="price" class="w-20">
<UInput v-model="item.price" type="number" />
</UFormField>
</UForm>
<div class="flex gap-2">
<UButton color="gray" variant="subtle" size="sm" @click="addItem()">
Add Item
</UButton>
<UButton color="gray" variant="ghost" size="sm" @click="removeItem()">
Remove Item
</UButton>
</div>
<div>
<UButton type="submit">
Submit
</UButton>
</div>
</UForm>
</template>

View File

@@ -0,0 +1,45 @@
<script setup lang="ts">
import type { FormError, FormErrorEvent, FormSubmitEvent } from '#ui/types'
const state = reactive({
email: undefined,
password: undefined
})
const validate = (state: any): FormError[] => {
const errors = []
if (!state.email) errors.push({ name: 'email', message: 'Required' })
if (!state.password) errors.push({ name: 'password', message: 'Required' })
return errors
}
const toast = useToast()
async function onSubmit(event: FormSubmitEvent<any>) {
toast.add({ title: 'Success', description: 'The form has been submitted.', color: 'green' })
console.log(event.data)
}
async function onError(event: FormErrorEvent) {
if (event?.errors?.[0]?.id) {
const element = document.getElementById(event.errors[0].id)
element?.focus()
element?.scrollIntoView({ behavior: 'smooth', block: 'center' })
}
}
</script>
<template>
<UForm :validate="validate" :state="state" class="space-y-4" @submit="onSubmit" @error="onError">
<UFormField label="Email" name="email">
<UInput v-model="state.email" />
</UFormField>
<UFormField label="Password" name="password">
<UInput v-model="state.password" type="password" />
</UFormField>
<UButton type="submit">
Submit
</UButton>
</UForm>
</template>

View File

@@ -0,0 +1,38 @@
<script setup lang="ts">
import * as v from 'valibot'
import type { FormSubmitEvent } from '#ui/types'
const schema = v.object({
email: v.pipe(v.string(), v.email('Invalid email')),
password: v.pipe(v.string(), v.minLength(8, 'Must be at least 8 characters'))
})
type Schema = v.InferOutput<typeof schema>
const state = reactive({
email: '',
password: ''
})
const toast = useToast()
async function onSubmit(event: FormSubmitEvent<Schema>) {
toast.add({ title: 'Success', description: 'The form has been submitted.', color: 'green' })
console.log(event.data)
}
</script>
<template>
<UForm :schema="v.safeParser(schema)" :state="state" class="space-y-4" @submit="onSubmit">
<UFormField label="Email" name="email">
<UInput v-model="state.email" />
</UFormField>
<UFormField label="Password" name="password">
<UInput v-model="state.password" type="password" />
</UFormField>
<UButton type="submit">
Submit
</UButton>
</UForm>
</template>

View File

@@ -0,0 +1,40 @@
<script setup lang="ts">
import { object, string, type InferType } from 'yup'
import type { FormSubmitEvent } from '#ui/types'
const schema = object({
email: string().email('Invalid email').required('Required'),
password: string()
.min(8, 'Must be at least 8 characters')
.required('Required')
})
type Schema = InferType<typeof schema>
const state = reactive({
email: undefined,
password: undefined
})
const toast = useToast()
async function onSubmit(event: FormSubmitEvent<Schema>) {
toast.add({ title: 'Success', description: 'The form has been submitted.', color: 'green' })
console.log(event.data)
}
</script>
<template>
<UForm :schema="schema" :state="state" class="space-y-4" @submit="onSubmit">
<UFormField label="Email" name="email">
<UInput v-model="state.email" />
</UFormField>
<UFormField label="Password" name="password">
<UInput v-model="state.password" type="password" />
</UFormField>
<UButton type="submit">
Submit
</UButton>
</UForm>
</template>

View File

@@ -0,0 +1,38 @@
<script setup lang="ts">
import { z } from 'zod'
import type { FormSubmitEvent } from '#ui/types'
const schema = z.object({
email: z.string().email('Invalid email'),
password: z.string().min(8, 'Must be at least 8 characters')
})
type Schema = z.output<typeof schema>
const state = reactive<Partial<Schema>>({
email: undefined,
password: undefined
})
const toast = useToast()
async function onSubmit(event: FormSubmitEvent<Schema>) {
toast.add({ title: 'Success', description: 'The form has been submitted.', color: 'green' })
console.log(event.data)
}
</script>
<template>
<UForm :schema="schema" :state="state" class="space-y-4" @submit="onSubmit">
<UFormField label="Email" name="email">
<UInput v-model="state.email" />
</UFormField>
<UFormField label="Password" name="password">
<UInput v-model="state.password" type="password" />
</UFormField>
<UButton type="submit">
Submit
</UButton>
</UForm>
</template>

View File

@@ -0,0 +1,31 @@
<script setup lang="ts">
const { data: users, status } = await useFetch('https://jsonplaceholder.typicode.com/users', {
transform: (data: { id: number, name: string }[]) => {
return data?.map(user => ({
label: user.name,
value: String(user.id),
avatar: { src: `https://i.pravatar.cc/120?img=${user.id}` }
})) || []
},
lazy: true
})
</script>
<template>
<UInputMenu
:items="users || []"
:loading="status === 'pending'"
icon="i-heroicons-user"
placeholder="Select user"
class="w-48"
>
<template #leading="{ modelValue, ui }">
<UAvatar
v-if="modelValue"
v-bind="modelValue.avatar"
:size="ui.itemLeadingAvatarSize()"
:class="ui.itemLeadingAvatar()"
/>
</template>
</UInputMenu>
</template>

View File

@@ -0,0 +1,37 @@
<script setup lang="ts">
const searchTerm = ref('')
const searchTermDebounced = refDebounced(searchTerm, 200)
const { data: users, status } = await useFetch('https://jsonplaceholder.typicode.com/users', {
params: { q: searchTermDebounced },
transform: (data: { id: number, name: string }[]) => {
return data?.map(user => ({
label: user.name,
value: String(user.id),
avatar: { src: `https://i.pravatar.cc/120?img=${user.id}` }
})) || []
},
lazy: true
})
</script>
<template>
<UInputMenu
v-model:search-term="searchTerm"
:items="users || []"
:loading="status === 'pending'"
:filter="false"
icon="i-heroicons-user"
placeholder="Select user"
class="w-48"
>
<template #leading="{ modelValue, ui }">
<UAvatar
v-if="modelValue"
v-bind="modelValue.avatar"
:size="ui.itemLeadingAvatarSize()"
:class="ui.itemLeadingAvatar()"
/>
</template>
</UInputMenu>
</template>

View File

@@ -0,0 +1,41 @@
<script setup lang="ts">
const { data: users, status } = await useFetch('https://jsonplaceholder.typicode.com/users', {
transform: (data: { id: number, name: string, email: string }[]) => {
return data?.map(user => ({
label: user.name,
email: user.email,
value: String(user.id),
avatar: { src: `https://i.pravatar.cc/120?img=${user.id}` }
})) || []
},
lazy: true
})
</script>
<template>
<UInputMenu
:items="users || []"
:loading="status === 'pending'"
:filter="['name', 'email']"
icon="i-heroicons-user"
placeholder="Select user"
class="w-80"
>
<template #leading="{ modelValue, ui }">
<UAvatar
v-if="modelValue"
v-bind="modelValue.avatar"
:size="ui.itemLeadingAvatarSize()"
:class="ui.itemLeadingAvatar()"
/>
</template>
<template #item-label="{ item }">
{{ item.label }}
<span class="text-gray-500 dark:text-gray-400">
{{ item.email }}
</span>
</template>
</UInputMenu>
</template>

View File

@@ -0,0 +1,14 @@
<script setup lang="ts">
const items = ref(['Backlog', 'Todo', 'In Progress', 'Done'])
const selected = ref('Backlog')
</script>
<template>
<UInputMenu
v-model="selected"
:items="items"
:ui="{
trailingIcon: 'group-data-[state=open]:rotate-180 transition-transform duration-200'
}"
/>
</template>

View File

@@ -0,0 +1,42 @@
<script setup lang="ts">
const items = ref([
{
label: 'benjamincanac',
value: 'benjamincanac',
avatar: {
src: 'https://github.com/benjamincanac.png',
alt: 'benjamincanac'
}
},
{
label: 'romhml',
value: 'romhml',
avatar: {
src: 'https://github.com/romhml.png',
alt: 'romhml'
}
},
{
label: 'noook',
value: 'noook',
avatar: {
src: 'https://github.com/noook.png',
alt: 'noook'
}
}
])
const selected = ref(items.value[0])
</script>
<template>
<UInputMenu v-model="selected" :items="items" class="w-40">
<template #leading="{ modelValue, ui }">
<UAvatar
v-if="modelValue"
v-bind="modelValue.avatar"
:size="ui.itemLeadingAvatarSize()"
:class="ui.itemLeadingAvatar()"
/>
</template>
</UInputMenu>
</template>

View File

@@ -0,0 +1,41 @@
<script setup lang="ts">
const items = ref([
{
label: 'bug',
value: 'bug',
chip: {
color: 'red' as const
}
},
{
label: 'enhancement',
value: 'enhancement',
chip: {
color: 'blue' as const
}
},
{
label: 'feature',
value: 'feature',
chip: {
color: 'violet' as const
}
}
])
const selected = ref(items.value[0])
</script>
<template>
<UInputMenu v-model="selected" :items="items" class="w-40">
<template #leading="{ modelValue, ui }">
<UChip
v-if="modelValue"
v-bind="modelValue.chip"
inset
standalone
:size="ui.itemLeadingChipSize()"
:class="ui.itemLeadingChip()"
/>
</template>
</UInputMenu>
</template>

View File

@@ -0,0 +1,29 @@
<script setup lang="ts">
const items = ref([
{
label: 'Backlog',
value: 'backlog',
icon: 'i-heroicons-question-mark-circle'
},
{
label: 'Todo',
value: 'todo',
icon: 'i-heroicons-plus-circle'
},
{
label: 'In Progress',
value: 'in_progress',
icon: 'i-heroicons-arrow-up-circle'
},
{
label: 'Done',
value: 'done',
icon: 'i-heroicons-check-circle'
}
])
const selected = ref(items.value[0])
</script>
<template>
<UInputMenu v-model="selected" :icon="selected?.icon" :items="items" class="w-40" />
</template>

View File

@@ -0,0 +1,13 @@
<script setup lang="ts">
const open = ref(false)
const items = ref(['Backlog', 'Todo', 'In Progress', 'Done'])
const selected = ref('Backlog')
defineShortcuts({
o: () => open.value = !open.value
})
</script>
<template>
<UInputMenu v-model="selected" v-model:open="open" :items="items" />
</template>

View File

@@ -0,0 +1,9 @@
<script setup lang="ts">
const searchTerm = ref('D')
const items = ref(['Backlog', 'Todo', 'In Progress', 'Done'])
const selected = ref('Backlog')
</script>
<template>
<UInputMenu v-model="selected" v-model:search-term="searchTerm" :items="items" />
</template>

View File

@@ -3,7 +3,7 @@ const searchTerm = ref('')
const { data: users, status } = await useFetch('https://jsonplaceholder.typicode.com/users', {
params: { q: searchTerm },
transform: (data: any[]) => {
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}` } })) || []
},
lazy: true
@@ -19,7 +19,12 @@ const groups = computed(() => [{
<template>
<UModal>
<UButton label="Search users..." color="gray" variant="subtle" icon="i-heroicons-magnifying-glass" />
<UButton
label="Search users..."
color="gray"
variant="subtle"
icon="i-heroicons-magnifying-glass"
/>
<template #content>
<UCommandPalette

View File

@@ -0,0 +1,27 @@
<script setup lang="ts">
const items = [
{
label: 'Guide',
icon: 'i-heroicons-book-open'
},
{
label: 'Composables',
icon: 'i-heroicons-circle-stack'
},
{
label: 'Components',
icon: 'i-heroicons-cube-transparent',
slot: 'components'
}
]
</script>
<template>
<UNavigationMenu :items="items" class="justify-center">
<template #components-trailing>
<UBadge label="44" variant="subtle" size="sm" />
</template>
</UNavigationMenu>
</template>

View File

@@ -0,0 +1,110 @@
<script setup lang="ts">
const items = [
{
label: 'Guide',
icon: 'i-heroicons-book-open',
children: [
{
label: 'Introduction',
description: 'Fully styled and customizable components for Nuxt.',
icon: 'i-heroicons-home'
},
{
label: 'Installation',
description: 'Learn how to install and configure Nuxt UI in your application.',
icon: 'i-heroicons-cloud-arrow-down'
},
{
label: 'Icons',
icon: 'i-heroicons-face-smile',
description: 'You have nothing to do, @nuxt/icon will handle it automatically.'
},
{
label: 'Colors',
icon: 'i-heroicons-swatch',
description: 'Choose a primary and a gray color from your Tailwind CSS theme.'
},
{
label: 'Theme',
icon: 'i-heroicons-cog',
description: 'You can customize components by using the `class` / `ui` props or in your app.config.ts.'
}
]
},
{
label: 'Composables',
icon: 'i-heroicons-circle-stack',
children: [
{
label: 'defineShortcuts',
icon: 'i-heroicons-document-text-20-solid',
description: 'Define shortcuts for your application.'
},
{
label: 'useModal',
icon: 'i-heroicons-document-text-20-solid',
description: 'Display a modal within your application.'
},
{
label: 'useSlideover',
icon: 'i-heroicons-document-text-20-solid',
description: 'Display a slideover within your application.'
},
{
label: 'useToast',
icon: 'i-heroicons-document-text-20-solid',
description: 'Display a toast within your application.'
}
]
},
{
label: 'Components',
icon: 'i-heroicons-cube-transparent',
children: [
{
label: 'Link',
icon: 'i-heroicons-document-text-20-solid',
description: 'Use NuxtLink with superpowers.'
},
{
label: 'Modal',
icon: 'i-heroicons-document-text-20-solid',
description: 'Display a modal within your application.'
},
{
label: 'NavigationMenu',
icon: 'i-heroicons-document-text-20-solid',
description: 'Display a list of links.'
},
{
label: 'Pagination',
icon: 'i-heroicons-document-text-20-solid',
description: 'Display a list of pages.'
},
{
label: 'Popover',
icon: 'i-heroicons-document-text-20-solid',
description: 'Display a non-modal dialog that floats around a trigger element.'
},
{
label: 'Progress',
icon: 'i-heroicons-document-text-20-solid',
description: 'Show a horizontal bar to indicate task progression.'
}
]
}
]
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)
})
</script>
<template>
<UNavigationMenu v-model="active" :items="items" class="justify-center" />
</template>

View File

@@ -0,0 +1,16 @@
<script setup lang="ts">
const page = ref(5)
function to(page: number) {
return {
query: {
page
},
hash: '#with-links'
}
}
</script>
<template>
<UPagination v-model:page="page" :total="100" :to="to" :sibling-count="1" show-edges />
</template>

View File

@@ -0,0 +1,40 @@
<script setup lang="ts">
const labels = [{
label: 'bug',
chip: {
color: 'red' as const
}
}, {
label: 'feature',
chip: {
color: 'green' as const
}
}, {
label: 'enhancement',
chip: {
color: 'blue' as const
}
}]
const label = ref([])
</script>
<template>
<UPopover :content="{ side: 'right', align: 'start' }">
<UButton
icon="i-heroicons-tag"
label="Select labels"
color="gray"
variant="subtle"
/>
<template #content>
<UCommandPalette
v-model="label"
multiple
placeholder="Search labels..."
:groups="[{ id: 'labels', items: labels }]"
:ui="{ input: '[&>input]:h-8' }"
/>
</template>
</UPopover>
</template>

View File

@@ -0,0 +1,31 @@
<script setup lang="ts">
const { data: users, status } = await useFetch('https://jsonplaceholder.typicode.com/users', {
transform: (data: { id: number, name: string }[]) => {
return data?.map(user => ({
label: user.name,
value: String(user.id),
avatar: { src: `https://i.pravatar.cc/120?img=${user.id}` }
})) || []
},
lazy: true
})
</script>
<template>
<USelectMenu
:items="users || []"
:loading="status === 'pending'"
icon="i-heroicons-user"
placeholder="Select user"
class="w-48"
>
<template #leading="{ modelValue, ui }">
<UAvatar
v-if="modelValue"
v-bind="modelValue.avatar"
:size="ui.itemLeadingAvatarSize()"
:class="ui.itemLeadingAvatar()"
/>
</template>
</USelectMenu>
</template>

View File

@@ -0,0 +1,37 @@
<script setup lang="ts">
const searchTerm = ref('')
const searchTermDebounced = refDebounced(searchTerm, 200)
const { data: users, status } = await useFetch('https://jsonplaceholder.typicode.com/users', {
params: { q: searchTermDebounced },
transform: (data: { id: number, name: string }[]) => {
return data?.map(user => ({
label: user.name,
value: String(user.id),
avatar: { src: `https://i.pravatar.cc/120?img=${user.id}` }
})) || []
},
lazy: true
})
</script>
<template>
<USelectMenu
v-model:search-term="searchTerm"
:items="users || []"
:loading="status === 'pending'"
:filter="false"
icon="i-heroicons-user"
placeholder="Select user"
class="w-48"
>
<template #leading="{ modelValue, ui }">
<UAvatar
v-if="modelValue"
v-bind="modelValue.avatar"
:size="ui.itemLeadingAvatarSize()"
:class="ui.itemLeadingAvatar()"
/>
</template>
</USelectMenu>
</template>

View File

@@ -0,0 +1,41 @@
<script setup lang="ts">
const { data: users, status } = await useFetch('https://jsonplaceholder.typicode.com/users', {
transform: (data: { id: number, name: string, email: string }[]) => {
return data?.map(user => ({
label: user.name,
email: user.email,
value: String(user.id),
avatar: { src: `https://i.pravatar.cc/120?img=${user.id}` }
})) || []
},
lazy: true
})
</script>
<template>
<USelectMenu
:items="users || []"
:loading="status === 'pending'"
:filter="['name', 'email']"
icon="i-heroicons-user"
placeholder="Select user"
class="w-80"
>
<template #leading="{ modelValue, ui }">
<UAvatar
v-if="modelValue"
v-bind="modelValue.avatar"
:size="ui.itemLeadingAvatarSize()"
:class="ui.itemLeadingAvatar()"
/>
</template>
<template #item-label="{ item }">
{{ item.label }}
<span class="text-gray-500 dark:text-gray-400">
{{ item.email }}
</span>
</template>
</USelectMenu>
</template>

View File

@@ -0,0 +1,14 @@
<script setup lang="ts">
const items = ref(['Backlog', 'Todo', 'In Progress', 'Done'])
const selected = ref('Backlog')
</script>
<template>
<USelectMenu
v-model="selected"
:items="items"
:ui="{
trailingIcon: 'group-data-[state=open]:rotate-180 transition-transform duration-200'
}"
/>
</template>

View File

@@ -0,0 +1,42 @@
<script setup lang="ts">
const items = ref([
{
label: 'benjamincanac',
value: 'benjamincanac',
avatar: {
src: 'https://github.com/benjamincanac.png',
alt: 'benjamincanac'
}
},
{
label: 'romhml',
value: 'romhml',
avatar: {
src: 'https://github.com/romhml.png',
alt: 'romhml'
}
},
{
label: 'noook',
value: 'noook',
avatar: {
src: 'https://github.com/noook.png',
alt: 'noook'
}
}
])
const selected = ref(items.value[0])
</script>
<template>
<USelectMenu v-model="selected" :items="items" class="w-40">
<template #leading="{ modelValue, ui }">
<UAvatar
v-if="modelValue"
v-bind="modelValue.avatar"
:size="ui.itemLeadingAvatarSize()"
:class="ui.itemLeadingAvatar()"
/>
</template>
</USelectMenu>
</template>

View File

@@ -0,0 +1,41 @@
<script setup lang="ts">
const items = ref([
{
label: 'bug',
value: 'bug',
chip: {
color: 'red' as const
}
},
{
label: 'enhancement',
value: 'enhancement',
chip: {
color: 'blue' as const
}
},
{
label: 'feature',
value: 'feature',
chip: {
color: 'violet' as const
}
}
])
const selected = ref(items.value[0])
</script>
<template>
<USelectMenu v-model="selected" :items="items" class="w-40">
<template #leading="{ modelValue, ui }">
<UChip
v-if="modelValue"
v-bind="modelValue.chip"
inset
standalone
:size="ui.itemLeadingChipSize()"
:class="ui.itemLeadingChip()"
/>
</template>
</USelectMenu>
</template>

View File

@@ -0,0 +1,29 @@
<script setup lang="ts">
const items = ref([
{
label: 'Backlog',
value: 'backlog',
icon: 'i-heroicons-question-mark-circle'
},
{
label: 'Todo',
value: 'todo',
icon: 'i-heroicons-plus-circle'
},
{
label: 'In Progress',
value: 'in_progress',
icon: 'i-heroicons-arrow-up-circle'
},
{
label: 'Done',
value: 'done',
icon: 'i-heroicons-check-circle'
}
])
const selected = ref(items.value[0])
</script>
<template>
<USelectMenu v-model="selected" :icon="selected?.icon" :items="items" class="w-40" />
</template>

View File

@@ -0,0 +1,13 @@
<script setup lang="ts">
const open = ref(false)
const items = ref(['Backlog', 'Todo', 'In Progress', 'Done'])
const selected = ref('Backlog')
defineShortcuts({
o: () => open.value = !open.value
})
</script>
<template>
<USelectMenu v-model="selected" v-model:open="open" :items="items" />
</template>

View File

@@ -0,0 +1,9 @@
<script setup lang="ts">
const searchTerm = ref('D')
const items = ref(['Backlog', 'Todo', 'In Progress', 'Done'])
const selected = ref('Backlog')
</script>
<template>
<USelectMenu v-model="selected" v-model:search-term="searchTerm" :items="items" />
</template>

View File

@@ -1,6 +1,6 @@
<script setup lang="ts">
const { data: users, status } = await useFetch('https://jsonplaceholder.typicode.com/users', {
transform: (data: { name: string, id: number }[]) => {
transform: (data: { id: number, name: string }[]) => {
return data?.map(user => ({
label: user.name,
value: String(user.id),

View File

@@ -0,0 +1,27 @@
<script setup lang="ts">
const toast = useToast()
const props = defineProps<{
description: string
}>()
function showToast() {
toast.add({
title: 'Uh oh! Something went wrong.',
description: props.description,
actions: [{
icon: 'i-heroicons-arrow-path-20-solid',
label: 'Retry',
color: 'gray',
variant: 'outline',
onClick: (e) => {
e?.stopPropagation()
}
}]
})
}
</script>
<template>
<UButton label="Show toast" color="gray" variant="outline" @click="showToast" />
</template>

View File

@@ -0,0 +1,21 @@
<script setup lang="ts">
import type { AvatarProps } from '@nuxt/ui'
const props = defineProps<{
avatar: AvatarProps
}>()
const toast = useToast()
function showToast() {
toast.add({
title: 'User invited',
description: 'benjamincanac was invited to the team.',
avatar: props.avatar
})
}
</script>
<template>
<UButton label="Invite user" color="gray" variant="outline" @click="showToast" />
</template>

View File

@@ -0,0 +1,20 @@
<script setup lang="ts">
const toast = useToast()
function showToast() {
toast.add({
title: 'Uh oh! Something went wrong.',
description: 'There was a problem with your request.',
icon: 'i-heroicons-wifi',
close: {
color: 'primary',
variant: 'outline',
class: 'rounded-full'
}
})
}
</script>
<template>
<UButton label="Show toast" color="gray" variant="outline" @click="showToast" />
</template>

View File

@@ -0,0 +1,19 @@
<script setup lang="ts">
const props = defineProps<{
closeIcon: string
}>()
const toast = useToast()
function showToast() {
toast.add({
title: 'Uh oh! Something went wrong.',
description: 'There was a problem with your request.',
closeIcon: props.closeIcon
})
}
</script>
<template>
<UButton label="Show toast" color="gray" variant="outline" @click="showToast" />
</template>

View File

@@ -0,0 +1,22 @@
<script setup lang="ts">
import type { ToastProps } from '@nuxt/ui'
const props = defineProps<{
color: ToastProps['color']
}>()
const toast = useToast()
function showToast() {
toast.add({
title: 'Uh oh! Something went wrong.',
description: 'There was a problem with your request.',
icon: 'i-heroicons-wifi',
color: props.color
})
}
</script>
<template>
<UButton label="Show toast" color="gray" variant="outline" @click="showToast" />
</template>

View File

@@ -0,0 +1,16 @@
<script setup lang="ts">
const props = defineProps<{
title: string
description: string
}>()
const toast = useToast()
function showToast() {
toast.add(props)
}
</script>
<template>
<UButton label="Show toast" color="gray" variant="outline" @click="showToast" />
</template>

View File

@@ -0,0 +1,18 @@
<script setup lang="ts">
const toast = useToast()
function addToCalendar() {
const eventDate = new Date(Date.now() + Math.random() * 31536000000)
const formattedDate = eventDate.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })
toast.add({
title: 'Event added to calendar',
description: `This event is scheduled for ${formattedDate}.`,
icon: 'i-heroicons-calendar-days'
})
}
</script>
<template>
<UButton label="Add to calendar" color="gray" variant="outline" icon="i-heroicons-plus" @click="addToCalendar" />
</template>

View File

@@ -0,0 +1,19 @@
<script setup lang="ts">
const props = defineProps<{
icon: string
}>()
const toast = useToast()
function showToast() {
toast.add({
title: 'Uh oh! Something went wrong.',
description: 'There was a problem with your request.',
icon: props.icon
})
}
</script>
<template>
<UButton label="Show toast" color="gray" variant="outline" @click="showToast" />
</template>

View File

@@ -0,0 +1,15 @@
<script setup lang="ts">
const props = defineProps<{
title: string
}>()
const toast = useToast()
function showToast() {
toast.add(props)
}
</script>
<template>
<UButton label="Show toast" color="gray" variant="outline" @click="showToast" />
</template>

View File

@@ -0,0 +1,26 @@
<script setup lang="ts">
const appConfig = useAppConfig()
</script>
<template>
<div>
<UFormField
label="toaster.duration"
size="sm"
class="inline-flex ring ring-gray-300 dark:ring-gray-700 rounded"
:ui="{
wrapper: 'bg-gray-50 dark:bg-gray-800/50 rounded-l flex border-r border-gray-300 dark:border-gray-700',
label: 'text-gray-500 dark:text-gray-400 px-2 py-1.5',
container: 'mt-0'
}"
>
<UInput
v-model="appConfig.toaster.duration"
color="gray"
variant="soft"
class="rounded rounded-l-none min-w-12"
:search-input="false"
/>
</UFormField>
</div>
</template>

View File

@@ -0,0 +1,27 @@
<script setup lang="ts">
const appConfig = useAppConfig()
</script>
<template>
<div>
<UFormField
label="toaster.expand"
size="sm"
class="inline-flex ring ring-gray-300 dark:ring-gray-700 rounded"
:ui="{
wrapper: 'bg-gray-50 dark:bg-gray-800/50 rounded-l flex border-r border-gray-300 dark:border-gray-700',
label: 'text-gray-500 dark:text-gray-400 px-2 py-1.5',
container: 'mt-0'
}"
>
<USelectMenu
v-model="appConfig.toaster.expand"
:items="[true, false]"
color="gray"
variant="soft"
class="rounded rounded-l-none min-w-12"
:search-input="false"
/>
</UFormField>
</div>
</template>

View File

@@ -0,0 +1,30 @@
<script setup lang="ts">
import theme from '#build/ui/toaster'
const positions = Object.keys(theme.variants.position)
const appConfig = useAppConfig()
</script>
<template>
<div>
<UFormField
label="toaster.position"
size="sm"
class="inline-flex ring ring-gray-300 dark:ring-gray-700 rounded"
:ui="{
wrapper: 'bg-gray-50 dark:bg-gray-800/50 rounded-l flex border-r border-gray-300 dark:border-gray-700',
label: 'text-gray-500 dark:text-gray-400 px-2 py-1.5',
container: 'mt-0'
}"
>
<USelectMenu
v-model="appConfig.toaster.position"
:items="positions"
color="gray"
variant="soft"
class="rounded rounded-l-none min-w-12"
:search-input="false"
/>
</UFormField>
</div>
</template>

View File

@@ -1,4 +1,4 @@
const useComponentExampleState = () => useState('component-example-state', () => ({}))
const useComponentExampleState = () => useState<Record<string, any>>('component-example-state', () => ({}))
export async function fetchComponentExample(name: string) {
const state = useComponentExampleState()
@@ -14,9 +14,9 @@ export async function fetchComponentExample(name: string) {
// Add to nitro prerender
if (import.meta.server) {
const event = useRequestEvent()
event.node.res.setHeader(
event?.node.res.setHeader(
'x-nitro-prerender',
[event.node.res.getHeader('x-nitro-prerender'), `/api/component-example/${name}.json`].filter(Boolean).join(',')
[event?.node.res.getHeader('x-nitro-prerender'), `/api/component-example/${name}.json`].filter(Boolean).join(',')
)
}

View File

@@ -1,6 +1,6 @@
import type { ComponentMeta } from 'vue-component-meta'
const useComponentsMetaState = () => useState('component-meta-state', () => ({}))
const useComponentsMetaState = () => useState<Record<string, any>>('component-meta-state', () => ({}))
export async function fetchComponentMeta(name: string): Promise<{ meta: ComponentMeta }> {
const state = useComponentsMetaState()
@@ -16,9 +16,9 @@ export async function fetchComponentMeta(name: string): Promise<{ meta: Componen
// Add to nitro prerender
if (import.meta.server) {
const event = useRequestEvent()
event.node.res.setHeader(
event?.node.res.setHeader(
'x-nitro-prerender',
[event.node.res.getHeader('x-nitro-prerender'), `/api/component-meta/${name}.json`].filter(Boolean).join(',')
[event?.node.res.getHeader('x-nitro-prerender'), `/api/component-meta/${name}.json`].filter(Boolean).join(',')
)
}

View File

@@ -1,6 +1,6 @@
<script setup lang="ts">
import type { NuxtError } from '#app'
import type { ContentSearchFile } from '@nuxt/ui-pro'
// import type { ContentSearchFile } from '@nuxt/ui-pro'
useSeoMeta({
title: 'Page not found',
@@ -18,7 +18,7 @@ const runtimeConfig = useRuntimeConfig()
const { integrity, api } = runtimeConfig.public.content
const { data: navigation } = await useAsyncData('navigation', () => fetchContentNavigation(), { default: () => [] })
const { data: files } = await useLazyFetch<ContentSearchFile[]>(`${api.baseURL}/search${integrity ? '.' + integrity : ''}`, { default: () => [] })
const { data: files } = await useLazyFetch<any[]>(`${api.baseURL}/search${integrity ? '.' + integrity : ''}`, { default: () => [] })
// Computed

View File

@@ -3,7 +3,7 @@ import type { NavItem } from '@nuxt/content'
const nav = inject<Ref<NavItem[]>>('navigation')
const navigation = computed(() => nav.value.filter(item => !item._path.startsWith('/pro')))
const navigation = computed(() => nav?.value.filter(item => !item._path.startsWith('/pro')))
</script>
<template>

10
docs/app/mdc.config.ts Normal file
View File

@@ -0,0 +1,10 @@
import { defineConfig } from '@nuxtjs/mdc/config'
import { transformerColorHighlight } from 'shiki-transformer-color-highlight'
export default defineConfig({
shiki: {
transformers: [
transformerColorHighlight()
]
}
})

View File

@@ -28,9 +28,9 @@ const { data: surround } = await useAsyncData(`${route.path}-surround`, () => {
const headline = computed(() => findPageHeadline(page.value))
useSeoMeta({
titleTemplate: '%s - Nuxt UI',
title: page.value.title,
ogTitle: `${page.value.title} - Nuxt UI`,
titleTemplate: '%s - Nuxt UI v3',
title: page.value.navigation?.title || page.value.title,
ogTitle: `${page.value.navigation?.title || page.value.title} - Nuxt UI v3`,
description: page.value.description,
ogDescription: page.value.description
})
@@ -39,25 +39,21 @@ defineOgImageComponent('Docs', {
headline: headline.value
})
// const communityLinks = computed(() => [{
// icon: 'i-heroicons-pencil-square',
// label: 'Edit this page',
// to: `https://github.com/nuxt/ui/edit/dev/docs/content/${page?.value?._file}`,
// target: '_blank'
// }, {
// icon: 'i-heroicons-star',
// label: 'Star on GitHub',
// to: 'https://github.com/nuxt/ui',
// target: '_blank'
// }, {
// icon: 'i-heroicons-lifebuoy',
// label: 'Contributing',
// to: '/getting-started/contributing'
// }, {
// label: 'Roadmap',
// icon: 'i-heroicons-map',
// to: '/roadmap'
// }])
const communityLinks = computed(() => [{
icon: 'i-heroicons-pencil-square',
label: 'Edit this page',
to: `https://github.com/nuxt/ui/edit/v3/docs/content/${page?.value?._file}`,
target: '_blank'
}, {
icon: 'i-heroicons-star',
label: 'Star on GitHub',
to: 'https://github.com/nuxt/ui',
target: '_blank'
}, {
label: 'Roadmap',
icon: 'i-heroicons-map',
to: '/roadmap'
}])
// const resourcesLinks = [{
// icon: 'i-simple-icons-figma',
@@ -79,32 +75,36 @@ defineOgImageComponent('Docs', {
<template>
<UPage v-if="page">
<UPageHeader :title="page.title" :description="page.description" :links="page.links" :headline="headline" />
<UPageHeader :title="page.title" :links="page.links" :headline="headline">
<template #description>
<MDC v-if="page.description" :value="page.description" unwrap="p" />
</template>
</UPageHeader>
<UPageBody>
<ContentRenderer v-if="page.body" :value="page" />
<USeparator />
<UContentSurround :surround="surround" />
<UContentSurround :surround="(surround as any)" />
</UPageBody>
<template v-if="page?.body?.toc?.links?.length" #right>
<UContentToc :links="page.body.toc.links">
<!-- <template #bottom>
<template #bottom>
<USeparator v-if="page.body?.toc?.links?.length" type="dashed" />
<UPageLinks title="Community" :links="communityLinks" />
<USeparator type="dashed" />
<!-- <USeparator type="dashed" />
<UPageLinks title="Resources" :links="resourcesLinks" />
<USeparator type="dashed" />
<AdsPro />
<AdsCarbon />
</template> -->
<AdsCarbon /> -->
</template>
</UContentToc>
</template>
</UPage>

View File

@@ -1,7 +1,7 @@
<script setup lang="ts">
const route = useRoute()
const name = route.params.slug[0]
const name = route.params.slug?.[0]
</script>
<template>

View File

@@ -0,0 +1,28 @@
<script setup lang="ts">
const title = 'Roadmap'
const description = 'Discover our Volta board for @nuxt/ui development status.'
useSeoMeta({
titleTemplate: '%s - Nuxt UI',
title,
ogTitle: 'Nuxt UI Roadmap',
description
})
defineOgImageComponent('Docs')
const appConfig = useAppConfig()
const colorMode = useColorMode()
const token = 'eyJ2aWV3IjoiYm9hcmQiLCJib2FyZFN0YXR1c2VzIjpbInRyaWFnZSIsImJhY2tsb2ciLCJ0b2RvIiwiaW5fcHJvZ3Jlc3MiLCJpbl9yZXZpZXciLCJkb25lIiwicmVsZWFzZWQiXSwiYm9hcmRMaW5rZWRQcnMiOmZhbHNlLCJsaXN0R3JvdXAiOiJzdGF0ZSIsImxpc3RPcmRlciI6ImNyZWF0ZWRfYXQiLCJ0aW1lbGluZVpvb20iOiJtb250aCIsInRpbWVsaW5lT3JkZXIiOiJzdGF0ZSIsInRpbWVsaW5lRGlzcGxheSI6ImFsbF9taWxlc3RvbmVzIiwiZmlsdGVycyI6e30sIm93bmVyIjoibnV4dCIsIm5hbWUiOiJ1aSJ9'
const src = computed(() => `https://volta.net/embed/${token}?theme=${colorMode.value}&gray=${appConfig.ui.colors.gray}&primary=${appConfig.ui.colors.primary}`)
</script>
<template>
<div class="h-[calc(100vh-var(--header-height)-var(--header-height)-1px)]">
<ClientOnly>
<iframe :src="src" width="100%" height="100%" />
</ClientOnly>
</div>
</template>

View File

@@ -4,11 +4,14 @@ export default defineNuxtPlugin({
const appConfig = useAppConfig()
if (import.meta.client) {
if (window.localStorage.getItem('nuxt-ui-primary')) {
appConfig.ui.colors.primary = window.localStorage.getItem('nuxt-ui-primary')
const primary = window.localStorage.getItem('nuxt-ui-primary')
if (primary) {
appConfig.ui.colors.primary = primary
}
if (window.localStorage.getItem('nuxt-ui-gray')) {
appConfig.ui.colors.gray = window.localStorage.getItem('nuxt-ui-gray')
const gray = window.localStorage.getItem('nuxt-ui-gray')
if (gray) {
appConfig.ui.colors.gray = gray
}
}

View File

@@ -8,7 +8,7 @@ export interface SimplePrettier {
function createPrettierWorkerApi(worker: Worker): SimplePrettier {
let counter = 0
const handlers = {}
const handlers: any = {}
worker.addEventListener('message', (event) => {
const { uid, message, error } = event.data
@@ -28,7 +28,7 @@ function createPrettierWorkerApi(worker: Worker): SimplePrettier {
}
})
function postMessage<T>(message) {
function postMessage<T>(message: any) {
const uid = ++counter
return new Promise<T>((resolve, reject) => {
handlers[uid] = [resolve, reject]

View File

@@ -1,7 +1,10 @@
/* eslint-disable no-undef */
import('https://unpkg.com/prettier@3.3.3/standalone.js')
import('https://unpkg.com/prettier@3.3.3/plugins/babel.js')
import('https://unpkg.com/prettier@3.3.3/plugins/estree.js')
import('https://unpkg.com/prettier@3.3.3/plugins/html.js')
import('https://unpkg.com/prettier@3.3.3/plugins/markdown.js')
import('https://unpkg.com/prettier@3.3.3/plugins/typescript.js')
self.onmessage = async function (event) {
self.postMessage({
@@ -19,6 +22,7 @@ function handleMessage(message) {
async function handleFormatMessage(message) {
const { options, source } = message
const formatted = await prettier.format(source, {
parser: 'markdown',
plugins: prettierPlugins,

View File

@@ -1,96 +1,96 @@
---
title: Introduction
description: Nuxt UI v3 - A powerful, Nuxt-integrated design system with enhanced components, flexibility, and developer experience.
navigation.title: Introduction
title: Nuxt UI v3
description: 'A comprehensive, Nuxt-integrated UI library providing a rich set of fully-styled, accessible and highly customizable components for building modern web applications.'
---
Welcome to Nuxt UI v3 beta! We're thrilled to introduce this major update to our UI library, bringing significant improvements and powerful new features tailored specifically for Nuxt applications.
We're thrilled to introduce this major update to our UI library, bringing significant improvements and powerful new features. Nuxt UI v3 represents a leap forward in creating robust, accessible, and highly customizable user interfaces for Nuxt applications.
## What's New in v3?
### Migration from Headless UI to Radix Vue
In v3, we've transitioned from Headless UI to Radix Vue as our core component foundation. This change brings several benefits:
### Radix Vue
- **Improved Accessibility**: WAI-ARIA compliant components with built-in keyboard navigation and focus management.
- **Enhanced Composability**: Unstyled, primitive components for greater flexibility and customization.
- **Vue and Nuxt Compatibility**: Seamless integration with Vue and Nuxt ecosystems.
- **Consistent API**: Enjoy a more unified and intuitive API across all components.
- **Active Development**: Benefit from ongoing improvements and new features in the Radix Vue ecosystem.
We've transitioned from [Headless UI](https://headlessui.com/) to [Radix Vue](https://www.radix-vue.com/) as our core component foundation. This shift brings several key advantages:
While this change may require some adjustments in your existing code, it significantly enhances the overall quality and capabilities of Nuxt UI.
- **Extensive Component Library**: With 55+ primitives, Radix Vue significantly expands our component offerings.
- **Active Development**: Radix Vue's growing popularity ensures ongoing improvements and updates.
- **Enhanced Accessibility**: Built-in accessibility features align with our commitment to inclusive design.
- **Vue 3 Optimization**: Seamless integration with Vue 3 and the Composition API.
### Tailwind CSS v4 Integration
Leverage the latest Tailwind CSS features for efficient styling and a streamlined development process.
This transition empowers Nuxt UI to become a more comprehensive and flexible UI library, offering developers greater power and customization options.
### Tailwind Variants Support
Create dynamic variants easily, allowing for more expressive and flexible component styling.
### Tailwind CSS v4
### Enhanced TypeScript Support
Enjoy improved type safety and an enhanced developer experience with our Nuxt-specific TypeScript optimizations.
Nuxt UI v3 integrates the latest Tailwind CSS v4 alpha (announced March 6, 2024), bringing significant improvements:
## Why Choose Nuxt UI v3?
- **Faster Builds**: Up to 10x faster, especially for larger projects.
- **Unified Toolchain**: Built-in features like vendor prefixing, nesting support, and modern CSS transforms.
- **CSS-First Configuration**: New `@theme` directive for easy customization without JavaScript.
- **Optimized Performance**: Smaller engine footprint and more efficient processing.
Nuxt UI v3 offers several advantages over using Radix Vue or Shadcn-vue directly:
::note
For a comprehensive overview of Tailwind CSS v4 alpha features, visit the [official announcement](https://tailwindcss.com/blog/tailwindcss-v4-alpha).
::
- **Nuxt-Specific Integration**: Seamless compatibility with Nuxt features like auto-imports and server-side rendering.
- **Pre-Styled, Customizable Components**: A complete set of components adhering to a cohesive design system, ready for use and customization.
- **Dark Mode Support**: Built-in, easy-to-implement dark mode for your entire application.
- **Nuxt Ecosystem Alignment**: Regular updates in sync with Nuxt releases, ensuring long-term compatibility.
- **Comprehensive Nuxt Documentation**: Detailed guides and examples specifically for Nuxt developers.
### Tailwind Variants
## Upcoming Vue Support
We've adopted [Tailwind Variants](https://www.tailwind-variants.org/) to manage our design system, offering:
We're excited to announce that in the near future, Nuxt UI v3 will also support Vue applications! This expansion will allow Vue developers to leverage the power and flexibility of Nuxt UI components in their projects, regardless of whether they're using Nuxt or standalone Vue.
- **Dynamic Styling**: Flexible component variants with a powerful API
- **Type Safety**: Full TypeScript support with auto-completion
- **Conflict Resolution**: Efficient merging of conflicting styles
Key points about the upcoming Vue support:
This integration unifies the styling of components, ensuring consistency and code maintainability.
- **Seamless Integration**: Use Nuxt UI components in your Vue projects with the same ease as in Nuxt applications.
- **Consistent API**: Enjoy the same intuitive API and component structure across both Vue and Nuxt projects.
- **Performance Optimizations**: Benefit from performance enhancements tailored for Vue applications.
- **Expanded Ecosystem**: Join a larger community of developers using Nuxt UI across different Vue-based projects.
### TypeScript Integration
Stay tuned for updates on the release of Vue support!
Nuxt UI v3 offers significantly improved TypeScript integration, providing a superior developer experience:
## Key Features
- **Enhanced Auto-completion**:
- Full auto-completion for component props based on your theme
- Intelligent suggestions for `app.config.ts` theme configuration
- **Fully Customizable**: Tailor components to fit your brand and design needs.
- **Responsive Design**: Create adaptive UIs across various device sizes.
- **Accessibility Focus**: Ensure your applications are usable by everyone.
- **TypeScript Support**: Enhanced type safety for Nuxt projects.
- **Generic-based Components**:
- Built using [Vue 3 Generics](https://vuejs.org/api/sfc-script-setup.html#generics)
- Improved type inference for slots and events
## Getting Started
- **Type-safe Theming**:
- Leveraging Tailwind Variants for type-safe styling options
- Customizable types for extended theme configurations
Ready to dive in? Follow our [installation guide](/getting-started/installation) to start using Nuxt UI v3 in your project. If you're upgrading from v2, check out our [migration guide](/getting-started/migration).
::note{to="/components/accordion#with-custom-slot"}
Check out an example of the Accordion component with auto-completion for props and slots.
::
## Frequently Asked Questions
## Migration
We want to be transparent: migrating from Nuxt UI v2 to v3 will require significant effort. While we've maintained core concepts and components, Nuxt UI v3 has been rebuilt from the ground up, resulting in a new library with enhanced capabilities.
Key points to consider:
- A comprehensive migration guide will be available in the coming weeks.
- Review the new documentation and components carefully before attempting to upgrade.
- If you encounter any issues, please report them on our [GitHub repository](https://github.com/nuxt/ui/issues).
## FAQ
::accordion
::accordion-item{label="Is Nuxt UI v3 compatible with my existing Nuxt UI v2 project?"}
While `Nuxt UI v3` introduces significant changes, we've aimed to maintain as much compatibility as possible. However, due to the transition from Headless UI to Radix Vue, some adjustments may be necessary. Please refer to our [migration guide](/getting-started/migration) for detailed information on upgrading your project.
::accordion-item{label="What are the main considerations when upgrading to Nuxt UI v3?"}
The transition to v3 involves significant changes, including new component structures, updated theming approaches, and revised TypeScript definitions. We recommend a careful, incremental upgrade process, starting with thorough testing in a development environment.
::
::accordion-item{label="What are the main differences between Nuxt UI v3 and v2?"}
The key differences include the transition from Headless UI to Radix Vue, improved accessibility, enhanced composability, Tailwind CSS v4 integration, and improved TypeScript support. These changes result in more flexible and powerful components with better performance and developer experience.
::accordion-item{label="Will Nuxt UI v3 work with other CSS frameworks like UnoCSS?"}
Nuxt UI v3 is currently designed to work exclusively with Tailwind CSS. While there's interest in UnoCSS support, implementing it would require significant changes to the theme structure due to differences in class naming conventions. As a result, we don't have plans to add UnoCSS support in v3.
::
::accordion-item{label="Will Nuxt UI v3 work with other CSS frameworks besides Tailwind CSS?"}
While Nuxt UI v3 is optimized for use with Tailwind CSS, the core components based on Radix Vue are unstyled. This means you can potentially use them with other CSS frameworks, although you may need to provide your own styling.
::accordion-item{label="Is Nuxt UI v3 compatible with standalone Vue projects?"}
We're planning to add Vue support in the near future. For now, Nuxt UI v3 is only available for Nuxt. Track progress on Vue compatibility [in this issue](https://github.com/nuxt/ui/issues/2129).
::
::accordion-item{label="How does the upcoming Vue support differ from the current Nuxt support?"}
The upcoming Vue support will allow you to use Nuxt UI components in standalone Vue projects without the Nuxt framework. The core functionality and API of the components will remain the same, but some Nuxt-specific features may not be available in Vue-only projects.
::
::accordion-item{label="Is Nuxt UI v3 suitable for production use?"}
As Nuxt UI v3 is currently in beta, we recommend thorough testing before using it in production environments. We're actively working on stabilizing the library and appreciate any feedback from early adopters.
::accordion-item{label="Is this version stable and suitable for production use?"}
As Nuxt UI v3 is currently in alpha, we recommend thorough testing before using it in production environments. We're actively working on stabilization and welcome feedback from early adopters to improve the library. Feel free to report any issues you encounter on our [GitHub repository](https://github.com/nuxt/ui/issues).
::
::
## Feedback and Contributions
:hr
Your input is invaluable as we refine Nuxt UI v3:
- Report issues or suggest improvements on our [GitHub repository](https://github.com/nuxt/ui)
- Join our [Discord community](https://discord.com/invite/nuxt) for discussions and support
- Follow us on [Twitter](https://twitter.com/nuxt_js) for the latest updates
Stay tuned for more exciting updates as we work towards the final release of Nuxt UI v3!
We're excited about the possibilities Nuxt UI v3 brings to your projects. Explore our documentation to learn more about new features, components, and best practices for building powerful, accessible user interfaces with Nuxt UI v3.

View File

@@ -1,22 +1,37 @@
---
title: Installation
description: 'Learn how to install and configure Nuxt UI in your application.'
navigation:
badge:
label: Todo
description: 'Learn how to install and configure Nuxt UI v3 in your application.'
---
## Setup
### Add to a Nuxt project
1. Install the Nuxt UI v3 alpha package:
1. Add `@nuxt/ui` module to your project:
::code-group
```bash
npx nuxi@latest module add ui
```bash [pnpm]
pnpm add @nuxt/ui@next
```
2. Add it to the `modules` section in your `nuxt.config.ts`:
```bash [yarn]
yarn add @nuxt/ui@next
```
```bash [npm]
npm install @nuxt/ui@next
```
```bash [bun]
bun add @nuxt/ui@next
```
::
::warning
Make sure you have `typescript` installed in your dev dependencies.
::
2. Register the Nuxt UI module in your `nuxt.config.ts`{lang="ts-type"}:
```ts [nuxt.config.ts]
export default defineNuxtConfig({
@@ -24,25 +39,34 @@ export default defineNuxtConfig({
})
```
That's it! You can now use all the components and composables in your Nuxt app ✨
3. Import Tailwind and Nuxt UI in your `app.vue`{lang="ts-type"} or in your [CSS](https://nuxt.com/docs/getting-started/styling#the-css-property):
### Use Nuxt starter
::code-group
[Nuxt Starter](https://nuxt.new/) template makes it easy to get started with Nuxt UI.
The Nuxt Starter template is available from the `nuxi init` command.
```bash
npx nuxi@latest init -t ui
```vue [app.vue]
<style>
@import "tailwindcss";
@import "@nuxt/ui";
</style>
```
Please check [nuxt/starter](https://github.com/nuxt/starter/tree/ui) for details.
```css [main.css]
@import "tailwindcss";
@import "@nuxt/ui";
```
::
## Options
You can customize Nuxt UI by providing options in your `nuxt.config`:
You can customize Nuxt UI by providing options in your `nuxt.config.ts`:
### `prefix`
Use the `prefix` option to change the prefix of the components.
- Default: `U`{lang="ts-type"}
```ts [nuxt.config.ts]
export default defineNuxtConfig({
modules: ['@nuxt/ui'],
@@ -52,41 +76,59 @@ export default defineNuxtConfig({
})
```
### `colors`
### `fonts`
Use the `fonts` option to enable or disable the `@nuxt/fonts` module.
- Default: `true`{lang="ts-type"}
```ts [nuxt.config.ts]
export default defineNuxtConfig({
modules: ['@nuxt/ui'],
ui: {
colors: ['blue', 'green', 'red']
fonts: false
}
})
```
### `transitions`
### `theme.colors`
Use the `theme.colors` option to choose which Tailwind CSS colors are used to generate classes for components.
- Default: `['red', 'orange', 'amber', 'yellow', 'lime', 'green', 'emerald', 'teal', 'cyan', 'sky', 'blue', 'indigo', 'violet', 'purple', 'fuchsia', 'pink', 'rose']`{lang="ts-type" class="inline"}
```ts [nuxt.config.ts]
export default defineNuxtConfig({
modules: ['@nuxt/ui'],
ui: {
transitions: false
theme: {
colors: ['blue', 'green', 'red']
}
}
})
```
## Edge
::note{to="/getting-started/colors#build-colors"}
This can help reduce the number of CSS classes generated in your bundle.
::
To use the latest updates pushed on the [`dev`](https://github.com/nuxt/ui/tree/dev) branch, you can use `@nuxt/ui-edge`.
### `theme.transitions`
Update your `package.json` to the following:
Use the `theme.transitions` option to disable all transitions on components.
```diff [package.json]
{
"devDependencies": {
- "@nuxt/ui": "^2.11.0"
+ "@nuxt/ui": "npm:@nuxt/ui-edge@latest"
- Default: `true`{lang="ts-type"}
```ts [nuxt.config.ts]
export default defineNuxtConfig({
modules: ['@nuxt/ui'],
ui: {
theme: {
transitions: false
}
}
}
})
```
Then run `pnpm install`, `yarn install` or `npm install`.
::note
This option adds the `transition-colors` class on components with hover or active states.
::

View File

@@ -1,56 +0,0 @@
---
description: ''
navigation:
badge:
label: Todo
---
Thanks to [`@nuxt/icon`](https://github.com/nuxt/icon), add 200,000+ ready to use icons to your Nuxt application based on [Iconify](https://iconify.design).
You can use any name from the https://icones.js.org collection such as the `i-` prefix (for example, `i-heroicons-cog`) with:
- any `icon` prop available across the components:
```vue
<template>
<UButton icon="i-heroicons-magnifying-glass" />
</template>
```
- the `UIcon` component to use icons anywhere:
```vue
<template>
<UIcon name="i-heroicons-moon" class="w-5 h-5 text-primary-500" />
</template>
```
### Collections
It's highly recommended to install the icons collections locally with:
::code-group
```bash [pnpm]
pnpm i @iconify-json/{collection_name}
```
```bash [yarn]
yarn add @iconify-json/{collection_name}
```
```bash [npm]
npm install @iconify-json/{collection_name}
```
::
For example, to use the `i-uil-github` icon, install it's collection with `@iconify-json/uil`. This way the icons can be served locally or from your serverless functions, which is faster and more reliable on both SSR and client-side.
::callout{icon="i-heroicons-light-bulb" to="https://github.com/nuxt/icon?tab=readme-ov-file#custom-local-collections" target="_blank"}
Read more about custom collections in the `@nuxt/icon` documentation.
::
## Theme
:icons-theme

View File

@@ -0,0 +1,226 @@
---
title: Theme
description: 'Learn to customize Nuxt UI components using Tailwind CSS v4 and the Tailwind Variants API.'
---
## Tailwind CSS
Since Nuxt UI v3 uses Tailwind CSS v4 alpha which doesn't have a documentation yet, let's have a look on how to use it.
Tailwind CSS v4 takes a CSS-first configuration approach, you now customize your theme with CSS variables inside a `@theme` directive:
```css [main.css]
@import "tailwindcss";
@import "@nuxt/ui";
@theme {
--font-family-sans: 'Public Sans', sans-serif;
--breakpoint-3xl: 1920px;
--color-green-50: #EFFDF5;
--color-green-100: #D9FBE8;
--color-green-200: #B3F5D1;
--color-green-300: #75EDAE;
--color-green-400: #00DC82;
--color-green-500: #00C16A;
--color-green-600: #00A155;
--color-green-700: #007F45;
--color-green-800: #016538;
--color-green-900: #0A5331;
--color-green-950: #052E16;
}
```
The `@theme` directive tells Tailwind to make new utilities and variants available based on those variables. It's the equivalent of the `theme.extend` key in Tailwind CSS v3 `tailwind.config.ts` file.
::note
You can learn more about this on [https://tailwindcss.com/blog/tailwindcss-v4-alpha](https://tailwindcss.com/blog/tailwindcss-v4-alpha#css-first-configuration).
::
This is exactly what the [`@import "@nuxt/ui";`](https://github.com/nuxt/ui/blob/v3/src/runtime/index.css) is all about, it extends the default Tailwind CSS theme and declares the `primary`, `error` and `gray` colors to be configurable through the [App Config](https://nuxt.com/docs/guide/directory-structure/app-config#app-config-file) but we'll talk more about that in the [Colors](/getting-started/colors) section.
## Tailwind Variants API
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:
### Slots
Components in Nuxt UI can have multiple `slots`, each representing a distinct HTML element or section within the component. These slots allow for flexible content insertion and styling. Let's take the [Card](/components/card) component as an example:
::code-group
```ts [src/theme/card.ts]
export default {
slots: {
root: 'bg-white dark:bg-gray-900 ring ring-gray-200 dark:ring-gray-800 divide-y divide-gray-200 dark:divide-gray-800 rounded-lg shadow',
header: 'p-4 sm:px-6',
body: 'p-4 sm:p-6',
footer: 'p-4 sm:px-6'
}
}
```
```vue [src/runtime/components/Card.vue]
<template>
<div :class="ui.root({ class: [props.class, props.ui?.root] })">
<div :class="ui.header({ class: props.ui?.header })">
<slot name="header" />
</div>
<div :class="ui.body({ class: props.ui?.body })">
<slot />
</div>
<div :class="ui.footer({ class: props.ui?.footer })">
<slot name="footer" />
</div>
</div>
</template>
```
::
Some components don't have slots, they are just composed of a single root element. In this case, the theme only defines the `base` slot like the [Container](/components/container) component for example:
::code-group
```ts [src/theme/container.ts]
export default {
base: 'max-w-7xl mx-auto px-4 sm:px-6 lg:px-8'
}
```
```vue [src/runtime/components/Container.vue]
<template>
<div :class="container({ class: props.class })">
<slot />
</div>
</template>
```
::
::caution
Components without slots don't have a [`ui` prop](#ui-prop), only the [`class` prop](#class-prop) is available to override styles.
::
### Variants
Nuxt UI components use `variants` to change the `slots` styles based on props. Here's an example of the [Avatar](/components/avatar) component:
```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-gray-100 dark:bg-gray-800',
image: 'h-full w-full rounded-[inherit] object-cover'
},
variants: {
size: {
'sm': {
root: 'size-7 text-sm'
},
'md': {
root: 'size-8 text-base'
},
'lg': {
root: 'size-9 text-lg'
}
}
},
defaultVariants: {
size: 'md'
}
}
```
This way, the `size` prop will apply the corresponding styles to the `root` slot:
::component-code{slug="avatar"}
---
ignore:
- src
props:
src: 'https://github.com/benjamincanac.png'
size: lg
---
::
The `defaultVariants` property specifies the default values for each variant. It determines how a component looks and behaves when no prop is provided. These default values can be customized in your [`app.config.ts`](#appconfigts) to adjust the standard appearance of components throughout your application.
## Customize components
You have multiple ways to customize the appearance of Nuxt UI components, you can do it for all components at once or on a per-component basis.
::tip
Tailwind Variants uses [tailwind-merge](https://github.com/dcastil/tailwind-merge) under the hood to merge classes so you don't have to worry about conflicting classes.
::
::note
You can explore the theme for each component in two ways:
- Check the `Theme` section in the documentation of each individual component.
- Browse the source code directly in the GitHub repository at https://github.com/nuxt/ui/tree/v3/src/theme.
::
### `app.config.ts`
You can override the theme of components inside your `app.config.ts` by using the exact same structure as the theme object.
Let's say you want to change the font weight of all your buttons, you can do it like this:
```ts [app.config.ts]
export default defineAppConfig({
ui: {
button: {
slots: {
base: 'font-bold'
}
}
}
})
```
In this example, the `font-bold` class will override the default `font-medium` class on all buttons.
### `ui` prop
You can also override a component's **slots** using the `ui` prop. This has priority over the `app.config.ts` configuration and `variants` resolution.
::component-code{slug="button"}
---
prettier: true
ignore:
- ui.leadingIcon
- color
- variant
- size
- icon
props:
icon: i-heroicons-magnifying-glass
size: md
color: gray
variant: outline
ui:
leadingIcon: 'text-primary-500 dark:text-primary-400 size-3'
slots:
default: |
Button
---
::
In this example, the `leadingIcon` slot is overwritten even though the `md` size variant would apply a `size-5` class by default.
### `class` prop
The `class` prop allows you to override the classes of the `root` or `base` slot. This has priority over the `app.config.ts` configuration and `variants` resolution.
::component-code{slug="button"}
---
props:
class: 'font-bold rounded-full'
slots:
default: Button
---
::

View File

@@ -1,68 +1,168 @@
---
description: 'Learn how to customize the look and feel of the components.'
navigation:
badge:
label: Todo
title: Colors
description: 'Learn to customize Nuxt UI component colors, enhancing your application visual theme.'
---
This module relies on Nuxt [App Config](https://nuxt.com/docs/guide/directory-structure/app-config#app-config-file) file to customize the look and feel of the components at runtime with HMR (hot-module-replacement).
## Build colors
### Configuration
Nuxt UI components provide dynamic `color` variants. By default, these variants classes are generated based on the default Tailwind CSS colors. Let's take the [Button](/components/button) component as an example:
Components are based on a `primary` and a `gray` color. You can change them in your `app.config.ts`.
::component-code{slug="button"}
---
props:
color: 'green'
slots:
default: Button
---
::
```ts [app.config.ts]
export default defineAppConfig({
You can change these colors with the [`theme.colors`](/getting-started/installation#themecolors) option in your `nuxt.config.ts` to select only the colors you're actually using.
For example, if you added a custom `cerise` color and only use the default `blue` and `green` colors in your application, you can configure the `colors` option like this:
::code-group
```ts [nuxt.config.ts]
export default defineNuxtConfig({
modules: ['@nuxt/ui'],
ui: {
primary: 'green',
gray: 'cool'
theme: {
colors: ['cerise', 'blue', 'green']
}
}
})
```
::tip
Try to change the `primary` and `gray` colors by clicking on the :u-icon{name="i-heroicons-swatch-20-solid" class="w-4 h-4 align-middle text-primary-500 dark:text-primary-400"} button in the header.
```css [main.css]
@import "tailwindcss";
@import "@nuxt/ui";
@theme {
--color-cerise-50: #FEF2F4;
--color-cerise-100: #FDE6E9;
--color-cerise-200: #FBD0D9;
--color-cerise-300: #F7AAB9;
--color-cerise-400: #F27A93;
--color-cerise-500: #E63F66;
--color-cerise-600: #D42A5B;
--color-cerise-700: #B21E4B;
--color-cerise-800: #951C45;
--color-cerise-900: #801B40;
--color-cerise-950: #470A1F;
}
```
::
As this module uses Tailwind CSS under the hood, you can use any of the [Tailwind CSS colors](https://tailwindcss.com/docs/customizing-colors#color-palette-reference) or your own custom colors. By default, the `primary` color is `green` and the `gray` color is `cool`.
::caution
Make sure to use color ranges from `50` to `950`. You can use tools like [UI Colors](https://uicolors.app/) to generate your palette.
::
When [using custom colors](https://tailwindcss.com/docs/customizing-colors#using-custom-colors) or [adding additional colors](https://tailwindcss.com/docs/customizing-colors#adding-additional-colors) through the `extend` key in your `tailwind.config.ts`, you'll need to make sure to define all the shades from `50` to `950` as most of them are used in the components config defined in [`ui.config/`](https://github.com/nuxt/ui/tree/dev/src/runtime/ui.config) directory. You can [generate your colors](https://tailwindcss.com/docs/customizing-colors#generating-colors) using tools such as https://uicolors.app/ for example.
This configuration will ensure that only classes for those three colors are generated in your final CSS bundle. When you use the `color` prop, it will be typed and provide autocompletion in your editor with those three colors.
```ts [tailwind.config.ts]
import type { Config } from 'tailwindcss'
import defaultTheme from 'tailwindcss/defaultTheme'
```vue
<template>
<UButton color="cerise">Button</UButton>
</template>
```
export default <Partial<Config>>{
theme: {
extend: {
colors: {
green: {
50: '#EFFDF5',
100: '#D9FBE8',
200: '#B3F5D1',
300: '#75EDAE',
400: '#00DC82',
500: '#00C16A',
600: '#00A155',
700: '#007F45',
800: '#016538',
900: '#0A5331',
950: '#052e16'
}
}
## Runtime colors
### Default aliases
Nuxt UI introduces three key color aliases used to style components:
- `primary`: Main brand color, used as the default color for components.
- Default: `green`{color="green"}
- `error`: For form error validation states.
- Default: `red`{color="red"}
- `gray`: Neutral color for backgrounds, text, etc.
- Default: `slate`
- `slate | cool | zinc | neutral | stone`
::warning{to="https://tailwindcss.com/docs/customizing-colors#default-color-palette" target="_blank"}
The Tailwind CSS `gray` color is renamed to `cool` in Nuxt UI to avoid conflicts with the `gray` alias.
::
You can configure these aliases in your `app.config.ts` file under the `ui.colors` key:
```ts [app.config.ts]
export default defineAppConfig({
ui: {
colors: {
primary: 'blue',
error: 'orange',
gray: 'zinc'
}
}
})
```
This powerful feature leverages Nuxt [App Config](https://nuxt.com/docs/guide/directory-structure/app-config#app-config-file), enabling dynamic styling of all components at runtime. It allows for real-time theme customization without requiring an application rebuild.
::tip
We recommend using these colors in your application whenever possible with classes like `text-primary-500 dark:text-primary-400`, `border-gray-200 dark:border-gray-800` or `bg-white dark:bg-gray-900` for example.
::
::important
These alias colors don't need to be explicitly listed in the `colors` option of your `nuxt.config.ts`. Also, if you've set `primary` to a custom color (e.g., `cerise`), you don't need to list `cerise` in the `colors` array.
::
::note
You can try this out by using the :prose-icon{name="i-heroicons-swatch-20-solid" class="text-primary-500 dark:text-primary-400"} menu in the header of this documentation.
::
### Custom aliases
You can also add your own color aliases to be configurable at runtime in your `app.config.ts` file:
1. Define the alias color by using CSS variables to let Tailwind know about it:
```css [main.css]
@import "tailwindcss";
@import "@nuxt/ui";
@theme {
--color-secondary-50: var(--color-secondary-50);
--color-secondary-100: var(--color-secondary-100);
--color-secondary-200: var(--color-secondary-200);
--color-secondary-300: var(--color-secondary-300);
--color-secondary-400: var(--color-secondary-400);
--color-secondary-500: var(--color-secondary-500);
--color-secondary-600: var(--color-secondary-600);
--color-secondary-700: var(--color-secondary-700);
--color-secondary-800: var(--color-secondary-800);
--color-secondary-900: var(--color-secondary-900);
--color-secondary-950: var(--color-secondary-950);
}
```
### CSS Variables
2. Set a default value for the color alias in your `app.config.ts` file:
To provide dynamic colors that can be changed at runtime, this module uses CSS variables. As Tailwind CSS already has a `gray` color, the module automatically renames it to `cool` to avoid conflicts (`coolGray` was renamed to `gray` when Tailwind CSS v3.0 was released).
```ts [app.config.ts]
export default defineAppConfig({
ui: {
colors: {
secondary: 'indigo'
}
}
})
```
Likewise, you can't define a `primary` color in your `tailwind.config.ts` as it would conflict with the `primary` color defined by the module.
3. Add this color to the `colors` option of your `nuxt.config.ts` file to generate classes:
::tip
We'd advise you to use those colors in your components and pages, e.g. `text-primary-500 dark:text-primary-400`, `bg-gray-100 dark:bg-gray-900`, etc. so your app automatically adapts when changing your `app.config.ts`.
::
```ts [nuxt.config.ts]
export default defineNuxtConfig({
modules: ['@nuxt/ui'],
ui: {
colors: ['secondary']
}
})
```
The `primary` color also has a `DEFAULT` shade that changes based on the theme. It is `500` in light mode and `400` in dark mode. You can use as a shortcut in your components and pages, e.g. `text-primary`, `bg-primary`, `focus-visible:ring-primary`, etc.
4. You can use the `secondary` color alias in your application and use classes like `text-secondary-500 dark:text-secondary-400`:
```vue
<template>
<UButton color="secondary">Button</UButton>
</template>
```

View File

@@ -0,0 +1,120 @@
---
title: Icons
description: 'Nuxt UI integrates with `@nuxt/icon` to access over 200,000+ icons from [Iconify](https://iconify.design/).'
links:
- label: 'nuxt/icon'
to: https://github.com/nuxt/icon
target: _blank
icon: i-simple-icons-github
---
## Usage
Nuxt UI automatically registers the `@nuxt/icon` module for you, so there's no additional setup required.
::note
You can use any name from the https://icones.js.org collection.
::
### Icon Component
You can use the [Icon](/components/icon) component with a `name` prop to display an icon:
::component-code{slug="icon"}
---
props:
name: 'i-heroicons-light-bulb'
class: 'size-5'
---
::
### Component Props
Some components also have an `icon` prop to display an icon, like the [Button](/components/button) for example:
::component-code{slug="button"}
---
ignore:
- color
- variant
props:
icon: i-heroicons-sun
variant: subtle
slots:
default: Button
---
::
## Collections
### Iconify Dataset
It's highly recommended to install the icon data locally with:
::code-group
```bash [pnpm]
pnpm i @iconify-json/{collection_name}
```
```bash [yarn]
yarn add @iconify-json/{collection_name}
```
```bash [npm]
npm install @iconify-json/{collection_name}
```
::
For example, to use the `i-uil-github` icon, install it's collection with `@iconify-json/uil`. This way the icons can be served locally or from your serverless functions, which is faster and more reliable on both SSR and client-side.
::tip{to="https://github.com/nuxt/icon?tab=readme-ov-file#iconify-dataset" target="_blank"}
Read more about this in the `@nuxt/icon` documentation.
::
### Custom Local Collections
You can use local SVG files to create a custom Iconify collection.
For example, place your icons' SVG files under a folder of your choice, for example, `./assets/icons`:
```bash
assets/icons
├── add.svg
└── remove.svg
```
In your `nuxt.config.ts`, add an item in `icon.customCollections`:
```ts
export default defineNuxtConfig({
modules: [
'@nuxt/ui'
],
icon: {
customCollections: [{
prefix: 'custom',
dir: './assets/icons'
}]
}
})
```
Then you can use the icons like this:
```vue
<template>
<UIcon name="i-custom-add" />
</template>
```
::tip{to="https://github.com/nuxt/icon?tab=readme-ov-file#custom-local-collections" target="_blank"}
Read more about this in the `@nuxt/icon` documentation.
::
## Theme
You can change the default icons used by Nuxt UI components in your `app.config.ts`:
:icons-theme

View File

@@ -1,207 +0,0 @@
---
description: 'Learn how to customize the look and feel of the components.'
navigation:
badge:
label: Todo
---
This module relies on Nuxt [App Config](https://nuxt.com/docs/guide/directory-structure/app-config#app-config-file) file to customize the look and feel of the components at runtime with HMR (hot-module-replacement).
## Colors
### Configuration
Components are based on a `primary` and a `gray` color. You can change them in your `app.config.ts`.
```ts [app.config.ts]
export default defineAppConfig({
ui: {
primary: 'green',
gray: 'cool'
}
})
```
::tip
Try to change the `primary` and `gray` colors by clicking on the :u-icon{name="i-heroicons-swatch-20-solid" class="w-4 h-4 align-middle text-primary-500 dark:text-primary-400"} button in the header.
::
As this module uses Tailwind CSS under the hood, you can use any of the [Tailwind CSS colors](https://tailwindcss.com/docs/customizing-colors#color-palette-reference) or your own custom colors. By default, the `primary` color is `green` and the `gray` color is `cool`.
When [using custom colors](https://tailwindcss.com/docs/customizing-colors#using-custom-colors) or [adding additional colors](https://tailwindcss.com/docs/customizing-colors#adding-additional-colors) through the `extend` key in your `tailwind.config.ts`, you'll need to make sure to define all the shades from `50` to `950` as most of them are used in the components config defined in [`ui.config/`](https://github.com/nuxt/ui/tree/dev/src/runtime/ui.config) directory. You can [generate your colors](https://tailwindcss.com/docs/customizing-colors#generating-colors) using tools such as https://uicolors.app/ for example.
```ts [tailwind.config.ts]
import type { Config } from 'tailwindcss'
import defaultTheme from 'tailwindcss/defaultTheme'
export default <Partial<Config>>{
theme: {
extend: {
colors: {
green: {
50: '#EFFDF5',
100: '#D9FBE8',
200: '#B3F5D1',
300: '#75EDAE',
400: '#00DC82',
500: '#00C16A',
600: '#00A155',
700: '#007F45',
800: '#016538',
900: '#0A5331',
950: '#052e16'
}
}
}
}
}
```
### CSS Variables
To provide dynamic colors that can be changed at runtime, this module uses CSS variables. As Tailwind CSS already has a `gray` color, the module automatically renames it to `cool` to avoid conflicts (`coolGray` was renamed to `gray` when Tailwind CSS v3.0 was released).
Likewise, you can't define a `primary` color in your `tailwind.config.ts` as it would conflict with the `primary` color defined by the module.
::tip
We'd advise you to use those colors in your components and pages, e.g. `text-primary-500 dark:text-primary-400`, `bg-gray-100 dark:bg-gray-900`, etc. so your app automatically adapts when changing your `app.config.ts`.
::
The `primary` color also has a `DEFAULT` shade that changes based on the theme. It is `500` in light mode and `400` in dark mode. You can use as a shortcut in your components and pages, e.g. `text-primary`, `bg-primary`, `focus-visible:ring-primary`, etc.
## Components
### `app.config.ts`
You can find the config of each component in the [`ui.config/`](https://github.com/nuxt/ui/tree/dev/src/runtime/ui.config) directory. You can override those classes in your own `app.config.ts`.
```ts [app.config.ts]
export default defineAppConfig({
ui: {
container: {
constrained: 'max-w-5xl'
}
}
})
```
Thanks to [tailwind-merge](https://github.com/dcastil/tailwind-merge), the `app.config.ts` is smartly merged with the default config. This means you don't have to rewrite everything.
You can change this behavior by setting `strategy` to `override` in your `app.config.ts`:
```ts [app.config.ts]
export default defineAppConfig({
ui: {
strategy: 'override',
button: {
color: {
white: {
solid: 'bg-white dark:bg-gray-900'
}
}
}
}
})
```
### `ui` prop
Each component has a `ui` prop that allows you to customize everything specifically.
```vue
<template>
<UContainer :ui="{ constrained: 'max-w-2xl' }">
<slot />
</UContainer>
</template>
```
::tip
You can find the default classes for each component under the `Config` section.
::
Thanks to [tailwind-merge](https://github.com/dcastil/tailwind-merge), the `ui` prop is smartly merged with the config. This means you don't have to rewrite everything.
For example, the default preset of the `FormGroup` component looks like this:
```json
{
"label": {
"base": "block font-medium text-gray-700 dark:text-gray-200"
}
}
```
To change the font of the `label`, you only need to write:
```vue
<UFormGroup name="email" label="Email" :ui="{ label: { base: 'font-semibold' } }" />
```
This will smartly replace the `font-medium` by `font-semibold` and prevent any class duplication and any class priority issue.
You can change this behavior by setting `strategy` to `override` inside the `ui` prop:
```vue
<UButton
to="https://github.com/nuxt/ui"
:ui="{
strategy: 'override',
color: {
white: {
solid: 'bg-white dark:bg-gray-900'
}
}
}"
/>
```
### `class` attribute
You can also use the `class` attribute to add classes to the component.
```vue
<template>
<UButton label="Button" class="rounded-full" />
</template>
```
Again, with [tailwind-merge](https://github.com/dcastil/tailwind-merge), this will smartly merge the classes with the `ui` prop and the config.
### Default values
Some component props like `size`, `color`, `variant`, etc. have a default value that you can override in your `app.config.ts`.
```ts [app.config.ts]
export default defineAppConfig({
ui: {
button: {
default: {
size: 'md',
color: 'gray',
variant: 'ghost'
}
}
}
})
```
## Dark mode
All the components are styled with dark mode in mind.
Thanks to [Tailwind CSS dark mode](https://tailwindcss.com/docs/dark-mode#toggling-dark-mode-manually) class strategy and the [@nuxtjs/color-mode](https://github.com/nuxt-modules/color-mode) module, you literally have nothing to do.
You can disable dark mode by setting the `preference` to `light` instead of `system` in your `nuxt.config.ts`.
```ts [nuxt.config.ts]
export default defineNuxtConfig({
colorMode: {
preference: 'light'
}
})
```
::tip
If you're stuck in dark mode even after changing this setting, you might need to remove the `nuxt-color-mode` entry from your browser's local storage.
::

View File

@@ -0,0 +1,43 @@
---
title: Fonts
description: 'Nuxt UI integrates with `@nuxt/fonts` to provide plug-and-play font optimization.'
links:
- label: 'nuxt/fonts'
to: https://github.com/nuxt/fonts
target: _blank
icon: i-simple-icons-github
---
## Usage
Nuxt UI automatically registers the `@nuxt/fonts` module for you, so there's no additional setup required. To use a font in your Nuxt UI application, you can simply declare it in your CSS:
::code-group
```vue [app.vue]
<style>
@import "tailwindcss";
@import "@nuxt/ui";
@theme {
--font-family-sans: 'Public Sans', sans-serif;
}
</style>
```
```css [main.css]
@import "tailwindcss";
@import "@nuxt/ui";
@theme {
--font-family-sans: 'Public Sans', sans-serif;
}
```
::
That's it! Nuxt Fonts will detect this and you should immediately see the web font loaded in your browser.
::tip{to="https://fonts.nuxt.com/advanced" target="_blank"}
Read more about how `@nuxt/fonts` work behind the scenes to optimize your fonts.
::

View File

@@ -0,0 +1,28 @@
---
title: Color Mode
description: 'Nuxt UI integrates with `@nuxtjs/color-mode` to allow for easy switching between light and dark themes.'
links:
- label: 'nuxtjs/color-mode'
to: https://github.com/nuxt-modules/color-mode
target: _blank
icon: i-simple-icons-github
navigation: false
---
## Usage
Nuxt UI automatically registers the `@nuxtjs/color-mode` module for you and takes advantage of [Tailwind CSS dark mode](https://tailwindcss.com/docs/dark-mode#toggling-dark-mode-manually) class strategy, so there's no additional setup required.
You can disable dark mode by setting the `preference` to `light` instead of `system` in your `nuxt.config.ts`.
```ts [nuxt.config.ts]
export default defineNuxtConfig({
colorMode: {
preference: 'light'
}
})
```
::tip
If you're stuck in dark mode even after changing this setting, you might need to remove the `nuxt-color-mode` entry from your browser's local storage.
::

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