Compare commits

...

93 Commits

Author SHA1 Message Date
Daniel Roe
c532a6f930 chore: bump to 0.1.9 2025-04-08 08:48:39 -07:00
Daniel Roe
6bbcb40c9e chore: bump to 0.1.8 2025-04-08 08:14:48 -07:00
Daniel Roe
befda895b9 build: bump vue-sfc-transformer 2025-04-07 19:25:13 -07:00
Benjamin Canac
d227a105d8 fix(CommandPalette): increase input font size to avoid zoom 2025-04-07 18:53:30 +02:00
Benjamin Canac
f6ff157bc4 chore(Textarea): apply same styles as Input
Partial revert of cb193f1d25
2025-04-07 13:58:30 +02:00
renovate[bot]
21fbd07639 chore(deps): update all non-major dependencies (v3) (#3800)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-05 11:12:58 +02:00
renovate[bot]
de234e8aeb chore(deps): update tailwindcss to ^4.1.3 (v3) (#3798)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-05 11:05:48 +02:00
Benjamin Canac
95a7707963 chore(module): improve meta 2025-04-05 10:50:19 +02:00
Benjamin Canac
24b54f6d9a chore(deps): update @nuxt/module-builder (#3799)
Co-authored-by: Daniel Roe <daniel@roe.dev>
2025-04-05 10:42:16 +02:00
Benjamin Canac
b3e37688d9 chore(module): update metas 2025-04-04 23:22:06 +02:00
Benjamin Canac
eeba3b4049 docs(app): clean seo metas 2025-04-04 23:12:17 +02:00
How Bizarre
a0c9731f63 feat(locale): add Bulgarian language (#3783)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2025-04-04 21:42:18 +02:00
Benjamin Canac
af1bf1bbde chore(NavigationMenu): remove slots types in createReusableTemplate 2025-04-04 17:14:51 +02:00
Benjamin Canac
54a7d04217 docs(showcase): update items 2025-04-04 17:14:51 +02:00
Vachmara
dfa2113db4 docs(showcase): fix contrast on light mode (#3790) 2025-04-04 12:11:12 +02:00
Benjamin Canac
abe0859691 fix(NavigationMenu): add sm:w-auto content slot
Resolves #3788
2025-04-04 11:42:47 +02:00
renovate[bot]
cf91f3c4cf chore(deps): update all non-major dependencies (v3) (#3768)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2025-04-04 11:10:48 +02:00
renovate[bot]
175fc73e63 chore(deps): update tailwindcss to ^4.1.2 (v3) (#3787)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-04 10:12:34 +02:00
Benjamin Canac
1d459803dc fix(Stepper): ui prop override on icon and content slots
Resolves #3785
2025-04-03 14:51:07 +02:00
Robin
60e2ee9a6c docs(Header): add GitHub link in mobile menu (#3721) 2025-04-03 12:26:43 +02:00
GuiFernandes
7f1c6caa6e docs(radio-group): items only accept strings or numbers (#3781) 2025-04-03 12:21:35 +02:00
Benjamin Canac
7ac17ae7e8 docs(showcase): update 2025-04-03 12:10:54 +02:00
Sandro Circi
52a97e2df7 fix(InputMenu/SelectMenu): support arbitrary value (#3779) 2025-04-02 22:28:09 +02:00
Benjamin Canac
041989549a docs(textarea): add badge on unreleased props 2025-04-02 18:06:01 +02:00
Benjamin Canac
31c37ce1a1 docs(radio-group): add badge on unreleased props
Resolves #3765, resolves nuxt/ui-pro#952
2025-04-02 18:04:29 +02:00
renovate[bot]
74cb2c3769 chore(deps): update tailwindcss to ^4.1.1 (v3) (#3770)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-02 14:39:29 +02:00
Benjamin Canac
5025e15d14 docs(app): improve images lazy loading 2025-04-02 14:28:35 +02:00
Hugo Richard
36c24ffe5c docs: add showcase page (#3659)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2025-04-02 13:06:46 +02:00
Benjamin Canac
b3a6b861cd docs(app): improve links 2025-04-02 12:25:36 +02:00
Benjamin Canac
c21eb32c70 docs: add team page 2025-04-02 12:20:26 +02:00
Benjamin Canac
44e6ba039d docs(drawer): add responsive example
Resolves #3772
2025-04-02 10:54:53 +02:00
renovate[bot]
ef75610244 chore(deps): update nuxt framework to ^3.16.2 (v3) (#3769)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-01 21:22:16 +02:00
Benjamin Canac
ffafd81e1e feat(Textarea): add resize-none class with autoresize prop 2025-04-01 12:44:50 +02:00
Benjamin Canac
06414d344b feat(Textarea): add autoresize-delay prop
Resolves #3730
2025-04-01 12:38:21 +02:00
Benjamin Canac
cb193f1d25 feat(Textarea): add icon, loading, etc. props to match Input 2025-04-01 12:38:21 +02:00
Benjamin Canac
4d8179ba08 chore(Input/InputNumber/PinInput/Textarea): clean functions order 2025-04-01 12:05:57 +02:00
Benjamin Canac
ce767c8429 chore(cli): fix tv import 2025-04-01 10:36:52 +02:00
Malik
1ae5cc09cb fix(ContextMenu/DropdownMenu): handle RTL mode (#3744) 2025-03-31 23:07:40 +02:00
Benjamin Canac
9d2fed1250 fix(InputMenu): emit change on multiple item removal
Fixes #3756
2025-03-31 21:14:43 +02:00
Benjamin Canac
924515ad07 chore(README): add contribution guide
Resolves #3757
2025-03-31 17:57:12 +02:00
Romain Hamel
4d138ad671 feat(RadioGroup): add card and table variants (#3178)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2025-03-31 16:34:31 +02:00
renovate[bot]
615fcfd73b chore(deps): update all non-major dependencies (v3) (#3755)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-31 14:13:04 +02:00
Robin Simonklein
f5e62849c9 feat(InputNumber): add support for stepSnapping & disableWheelChange props (#3731) 2025-03-31 13:59:47 +02:00
Daniele Nicosia
f25fed58e9 fix(InputMenu/SelectMenu): correctly call onSelect events (#3735) 2025-03-31 13:58:42 +02:00
renovate[bot]
ca15bc0c75 chore(deps): update all non-major dependencies (v3) (#3722)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-31 13:55:40 +02:00
renovate[bot]
29f004db95 chore(deps): lock file maintenance (v3) (#3746)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-31 10:52:25 +02:00
Norbiros
97274f15b8 fix(ContextMenuContent/DropdownMenuContent): remove unwanted any (#3741) 2025-03-30 19:41:38 +02:00
Benjamin Canac
8471fb9fa4 docs(ThemePicker): wrong neutral color 2025-03-28 23:01:04 +01:00
Benjamin Canac
9ec159e207 chore(tsconfig): ignore playground-vue 2025-03-28 19:06:02 +01:00
Benjamin Canac
0456670dac feat(PinInput): add autofocus / autofocus-delay props
Resolves #3717
2025-03-28 19:05:10 +01:00
Benjamin Canac
f05dbd26d1 chore(release): v3.0.2 2025-03-28 16:57:21 +01:00
Benjamin Canac
9be36d328c playground(app): add title 2025-03-28 16:55:11 +01:00
renovate[bot]
4f28e1fe96 chore(deps): update all non-major dependencies (v3) (#3698)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-28 16:43:12 +01:00
Eugen Istoc
bd99c2d850 fix(useOverlay): refine open method type to infer close emit return type (#3716) 2025-03-28 16:42:48 +01:00
Benjamin Canac
23bfeb9370 fix(vue): mock nuxtApp.hooks & useRuntimeHook 2025-03-28 15:07:54 +01:00
仿生狮子
88f349d0d7 fix(Avatar): proxy $attrs to default slot (#3712) 2025-03-28 10:27:25 +01:00
Daniele Nicosia
e7e6745599 fix(types): add missing export for ButtonGroup (#3709) 2025-03-27 23:41:33 +01:00
Benjamin Canac
d2c832292a chore(deps): update @nuxt/ui-pro 2025-03-27 23:22:44 +01:00
Benjamin Canac
bc61d29cce fix(CommandPalette): use group.id as key 2025-03-27 21:32:38 +01:00
Malik
777fe4a309 playground: improve rtl (#3707) 2025-03-27 19:18:58 +01:00
Benjamin Canac
3074632523 fix(InputMenu): reset searchTerm on update:open
Resolves #3620
2025-03-27 16:27:48 +01:00
Benjamin Canac
94b6e520f5 fix(InputMenu/SelectMenu): empty search results 2025-03-27 12:18:46 +01:00
Benjamin Canac
754dd36473 docs(SupportedLanguages): reorder mapping 2025-03-27 12:16:39 +01:00
Benjamin Canac
67a52b6f4e test(Table): update snapshots 2025-03-26 17:40:36 +01:00
Benjamin Canac
2e7d4989b5 chore(github): prevent running module nuxt-ui-pro job on forks 2025-03-26 17:33:32 +01:00
Benjamin Canac
c07a79571f docs(slug): add purchase and affilitate in community links 2025-03-26 17:31:24 +01:00
Benjamin Canac
4cebdb5152 chore(deps): update @nuxt/ui-pro 2025-03-26 17:31:19 +01:00
Benjamin Canac
06dfb963be chore(CommandPalette): improve placeholder tsdoc 2025-03-26 17:31:19 +01:00
Benjamin Canac
4ebb94cd7e fix(Table): wrong condition on caption slot 2025-03-26 17:31:19 +01:00
Benjamin Canac
afff54fecd feat(Table): add empty prop 2025-03-26 17:31:18 +01:00
renovate[bot]
7ec08017e0 chore(deps): update all non-major dependencies (v3) (#3695)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-26 15:36:26 +01:00
Romain Hamel
3dd88bacec fix(Form): clear dirty state after submit (#3692) 2025-03-26 13:48:28 +01:00
renovate[bot]
d6d7971d44 chore(deps): update tailwindcss to ^4.0.17 (v3) (#3690)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-26 13:48:02 +01:00
Romain Hamel
20c33920d0 fix(FormField): add help to aria-describedby attribute (#3691) 2025-03-26 13:47:38 +01:00
Stijn Slats
a63047b79a docs(accordion): add drag and drop example (#3684)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2025-03-26 11:07:47 +01:00
renovate[bot]
f3e32ba5a2 chore(deps): update all non-major dependencies (v3) (#3667)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-26 10:59:21 +01:00
renovate[bot]
bd3f54aa80 chore(deps): update tailwindcss to ^4.0.16 (v3) (#3682)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-26 10:44:06 +01:00
Hugo Richard
9509c37af8 docs: improve lighthouse score and accessibility (#3673) 2025-03-25 16:23:35 +01:00
Benjamin Canac
df00149598 fix(Container): add w-full class 2025-03-25 16:23:05 +01:00
Bobbie Goede
f72c886d3a docs(i18n): remove next tag from @nuxtjs/i18n installation (#3675) 2025-03-25 10:07:58 +01:00
Benjamin Canac
c531d0248b fix(Link): handle aria-current like NuxtLink / RouterLink 2025-03-24 23:57:46 +01:00
Benjamin Canac
d73768b704 fix(Link): prevent active="true" binding on html 2025-03-24 23:56:28 +01:00
Sandro Circi
b9983549a4 fix(components): improve generic types (#3331)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2025-03-24 21:38:13 +01:00
Benjamin Canac
370054b20c fix(Link): proxy onClick
Resolves #3631
2025-03-24 19:08:09 +01:00
Hugo Richard
4a2b77d86c feat(Calendar): allow year and month buttons styling (#3672) 2025-03-24 19:00:28 +01:00
Benjamin Canac
ade16b76cf fix(Link): properly pick all aria-* & data-* attrs 2025-03-24 18:58:49 +01:00
Benjamin Canac
1babad4f74 chore(github): update stale to close needs reproduction 2025-03-24 16:22:22 +01:00
Benjamin Canac
8fb8dc29cf docs(nuxt.config): update vite.optimizeDeps 2025-03-24 16:22:22 +01:00
renovate[bot]
e0ec60d1b1 chore(deps): update devdependency wrangler to v4 (v3) (#3554)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-24 15:36:34 +01:00
Benjamin Canac
3db3058395 docs(index): improve marquees performances 2025-03-24 15:33:01 +01:00
Benjamin Canac
0095d8916b fix(NavigationMenu): add z-index on viewport
Resolves #3654
2025-03-24 15:11:47 +01:00
Sébastien Chopin
58ae62413d docs: add missing redirect 2025-03-24 14:49:05 +01:00
Benjamin Canac
1965495768 chore(deps): remove typescript resolution 2025-03-24 14:41:50 +01:00
239 changed files with 8031 additions and 3871 deletions

View File

@@ -160,6 +160,9 @@ jobs:
nuxt-ui-pro: nuxt-ui-pro:
needs: build needs: build
# Only run this job if not a fork PR (when push event or PR from same repo)
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
permissions: permissions:

View File

@@ -10,14 +10,16 @@ jobs:
permissions: permissions:
issues: write issues: write
pull-requests: write
steps: steps:
- uses: actions/stale@v9 - uses: actions/stale@v9
with: with:
exempt-issue-labels: triage,v3 days-before-stale: -1 # Issues and PR will never be flagged stale automatically.
stale-issue-message: 'This issue is stale because it has been open for 30 days with no activity.' stale-issue-label: 'needs reproduction' # Label that flags an issue as stale.
stale-issue-label: stale only-labels: 'needs reproduction' # Only process these issues
stale-pr-label: stale days-before-issue-close: 7
days-before-stale: 30 ignore-updates: true
days-before-close: -1 remove-stale-when-updated: false
close-issue-message: This issue was closed because it was open for 7 days without a reproduction.
close-issue-label: closed-by-bot
operations-per-run: 300 #default 30

View File

@@ -1,5 +1,38 @@
# Changelog # Changelog
## [3.0.2](https://github.com/nuxt/ui/compare/v3.0.1...v3.0.2) (2025-03-28)
### Features
* **Calendar:** allow year and month buttons styling ([#3672](https://github.com/nuxt/ui/issues/3672)) ([4a2b77d](https://github.com/nuxt/ui/commit/4a2b77d86c28806234002340eda39de4dc78cce0))
* **locale:** add Armenian language ([#3664](https://github.com/nuxt/ui/issues/3664)) ([c76f590](https://github.com/nuxt/ui/commit/c76f5900970e3f5c451192b1207ccea04771e8b3))
* **Table:** add `empty` prop ([afff54f](https://github.com/nuxt/ui/commit/afff54fecd31497238461e0a44abd8668ed734c3))
### Bug Fixes
* **Avatar:** proxy `$attrs` to default slot ([#3712](https://github.com/nuxt/ui/issues/3712)) ([88f349d](https://github.com/nuxt/ui/commit/88f349d0d74eb1c2ce5066818731759c25a9e83e))
* **Button:** use `focus:outline-none` instead of `focus:outline-hidden` ([c231fe5](https://github.com/nuxt/ui/commit/c231fe5f26ca7614df46a7ec8a5ce7f4ec8884e7)), closes [#3658](https://github.com/nuxt/ui/issues/3658)
* **CommandPalette:** use `group.id` as key ([bc61d29](https://github.com/nuxt/ui/commit/bc61d29cce531715a6279444845f02a002a22af7))
* **components:** improve generic types ([#3331](https://github.com/nuxt/ui/issues/3331)) ([b998354](https://github.com/nuxt/ui/commit/b9983549a4b743724ea3ef99cc4a243f5ca41e53))
* **Container:** add `w-full` class ([df00149](https://github.com/nuxt/ui/commit/df001495980647cab1e67fd16154f1bc778de5e2))
* **defineLocale/defineShortcuts:** remove `@__NO_SIDE_EFFECTS__` ([82e2665](https://github.com/nuxt/ui/commit/82e26655a40782555299516f32a76046fa0dbd3a))
* **Drawer:** remove `fadeFromIndex` prop proxy ([f7604e5](https://github.com/nuxt/ui/commit/f7604e565f717001a4d4c2974cf23559a3f01c21))
* **Form:** clear dirty state after submit ([#3692](https://github.com/nuxt/ui/issues/3692)) ([3dd88ba](https://github.com/nuxt/ui/commit/3dd88bacecb2945efba8cc3cb4fe59fcbc056e9a))
* **FormField:** add `help` to `aria-describedby` attribute ([#3691](https://github.com/nuxt/ui/issues/3691)) ([20c3392](https://github.com/nuxt/ui/commit/20c33920d005332db3c83f33a8c54c7c227ce0a0))
* **InputMenu/SelectMenu:** empty search results ([94b6e52](https://github.com/nuxt/ui/commit/94b6e520f5ccf011204e953421fcc5b44b637e51))
* **InputMenu:** reset `searchTerm` on `update:open` ([3074632](https://github.com/nuxt/ui/commit/3074632523e67fa6a0ad3d9a71e5692c285bdc3a)), closes [#3620](https://github.com/nuxt/ui/issues/3620)
* **Link:** handle `aria-current` like `NuxtLink` / `RouterLink` ([c531d02](https://github.com/nuxt/ui/commit/c531d0248be7863980a1f676643c2dea8301c009))
* **Link:** prevent `active="true"` binding on html ([d73768b](https://github.com/nuxt/ui/commit/d73768b70453d60dd4186a996c1cf808b0294bf6))
* **Link:** properly pick all `aria-*` & `data-*` attrs ([ade16b7](https://github.com/nuxt/ui/commit/ade16b76cf535924a8d0f402b4d5d65cb67a55eb))
* **Link:** proxy `onClick` ([370054b](https://github.com/nuxt/ui/commit/370054b20c0201c9dba84ddfcd1e916594619b93)), closes [#3631](https://github.com/nuxt/ui/issues/3631)
* **NavigationMenu:** add `z-index` on viewport ([0095d89](https://github.com/nuxt/ui/commit/0095d8916bf361c0c89972e2f86b79850510c6a9)), closes [#3654](https://github.com/nuxt/ui/issues/3654)
* **Switch:** prevent transition on focus outline ([68787b2](https://github.com/nuxt/ui/commit/68787b26fdf2bd5f9d9e812e5bfddb19abe45d1d))
* **Table:** wrong condition on `caption` slot ([4ebb94c](https://github.com/nuxt/ui/commit/4ebb94cd7ef909b3547bce0922f75fe3ff74de4c))
* **Tabs:** remove `focus:outline-hidden` class ([1769d5e](https://github.com/nuxt/ui/commit/1769d5ed6ea46b1f7eafdc48cb6456512229f98b))
* **types:** add missing export for ButtonGroup ([#3709](https://github.com/nuxt/ui/issues/3709)) ([e7e6745](https://github.com/nuxt/ui/commit/e7e674559981177ad08be42418746060d7737df9))
* **useOverlay:** refine `open` method type to infer close emit return type ([#3716](https://github.com/nuxt/ui/issues/3716)) ([bd99c2d](https://github.com/nuxt/ui/commit/bd99c2d850d57baccc51e049c0b578a6fc6ab431))
* **vue:** mock `nuxtApp.hooks` & `useRuntimeHook` ([23bfeb9](https://github.com/nuxt/ui/commit/23bfeb937004d619187a67fb43e4c76b13d00069))
## [3.0.1](https://github.com/nuxt/ui/compare/v3.0.0...v3.0.1) (2025-03-21) ## [3.0.1](https://github.com/nuxt/ui/compare/v3.0.0...v3.0.1) (2025-03-21)
### ⚠ BREAKING CHANGES ### ⚠ BREAKING CHANGES

View File

@@ -104,6 +104,17 @@ app.mount('#app')
Learn more in the [installation guide](https://ui.nuxt.com/getting-started/installation/vue). Learn more in the [installation guide](https://ui.nuxt.com/getting-started/installation/vue).
## Contribution
Thank you for considering contributing to Nuxt UI. Here are a few ways you can get involved:
- Reporting Bugs: If you come across any bugs or issues, please check out the reporting bugs guide to learn how to submit a bug report.
- Suggestions: Have any thoughts to enhance Nuxt UI? We'd love to hear them! Check out the [contribution guide](https://ui.nuxt.com/getting-started/contribution) to share your suggestions.
## Local Development
Follow the docs to [set up your local development environment](https://ui.nuxt.com/getting-started/contribution#local-development) and contribute.
## Credits ## Credits
- [nuxt/nuxt](https://github.com/nuxt/nuxt) - [nuxt/nuxt](https://github.com/nuxt/nuxt)

View File

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

View File

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

View File

@@ -12,6 +12,7 @@ const { data: files } = useLazyAsyncData('search', () => queryCollectionSearchSe
}) })
const links = useLinks() const links = useLinks()
const searchLinks = useSearchLinks()
const color = computed(() => colorMode.value === 'dark' ? (colors as any)[appConfig.ui.colors.neutral][900] : 'white') const color = computed(() => colorMode.value === 'dark' ? (colors as any)[appConfig.ui.colors.neutral][900] : 'white')
const radius = computed(() => `:root { --ui-radius: ${appConfig.theme.radius}rem; }`) const radius = computed(() => `:root { --ui-radius: ${appConfig.theme.radius}rem; }`)
const blackAsPrimary = computed(() => appConfig.theme.blackAsPrimary ? `:root { --ui-primary: black; } .dark { --ui-primary: white; }` : ':root {}') const blackAsPrimary = computed(() => appConfig.theme.blackAsPrimary ? `:root { --ui-primary: black; } .dark { --ui-primary: white; }` : ':root {}')
@@ -64,6 +65,7 @@ provide('navigation', mappedNavigation)
<ClientOnly> <ClientOnly>
<LazyUContentSearch <LazyUContentSearch
:links="searchLinks"
:files="files" :files="files"
:groups="[{ :groups="[{
id: 'framework', id: 'framework',

View File

@@ -38,7 +38,7 @@ onMounted(() => {
} }
.carbon-poweredby { .carbon-poweredby {
@apply block text-[10px] text-center text-(--ui-text-dimmed) pt-2; @apply block text-xs text-center text-(--ui-text-muted) pt-2;
} }
&:hover { &:hover {

View File

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

View File

@@ -22,8 +22,20 @@ onMounted(() => {
const navigation = inject<Ref<ContentNavigationItem[]>>('navigation') const navigation = inject<Ref<ContentNavigationItem[]>>('navigation')
const githubLink = computed(() => {
return `https://github.com/nuxt/${value.value}`
})
const desktopLinks = computed(() => props.links.map(({ icon, ...link }) => link)) const desktopLinks = computed(() => props.links.map(({ icon, ...link }) => link))
const mobileLinks = computed(() => props.links.map(link => ({ ...link, defaultOpen: link.children && route.path.startsWith(link.to as string) }))) const mobileLinks = computed(() => [
...props.links.map(link => ({ ...link, defaultOpen: link.children && route.path.startsWith(link.to as string) })),
{
label: 'Open on GitHub',
to: githubLink.value,
icon: 'i-simple-icons-github',
target: '_blank'
}
])
</script> </script>
<template> <template>
@@ -73,7 +85,7 @@ const mobileLinks = computed(() => props.links.map(link => ({ ...link, defaultOp
:key="value" :key="value"
color="neutral" color="neutral"
variant="ghost" variant="ghost"
:to="`https://github.com/nuxt/${value}`" :to="githubLink"
target="_blank" target="_blank"
icon="i-simple-icons-github" icon="i-simple-icons-github"
aria-label="GitHub" aria-label="GitHub"

View File

@@ -14,6 +14,7 @@ const props = withDefaults(defineProps<{
color?: string color?: string
size?: { min: number, max: number } size?: { min: number, max: number }
speed?: 'slow' | 'normal' | 'fast' speed?: 'slow' | 'normal' | 'fast'
isIndex?: boolean
}>(), { }>(), {
starCount: 50, starCount: 50,
color: 'var(--ui-primary)', color: 'var(--ui-primary)',
@@ -21,7 +22,8 @@ const props = withDefaults(defineProps<{
min: 1, min: 1,
max: 3 max: 3
}), }),
speed: 'normal' speed: 'normal',
isIndex: false
}) })
const route = useRoute() const route = useRoute()
@@ -53,7 +55,7 @@ const twinkleDuration = computed(() => {
</script> </script>
<template> <template>
<div class="absolute pointer-events-none z-[-1] inset-y-0 left-4 right-4 lg:right-[50%] overflow-hidden"> <div class="absolute pointer-events-none z-[-1] overflow-hidden" :class="isIndex ? 'inset-y-0 left-4 right-4 lg:right-[50%]' : 'inset-0'">
<div <div
v-for="star in stars" v-for="star in stars"
:key="star.id" :key="star.id"

View File

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

View File

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

View File

@@ -12,14 +12,15 @@ function getEmojiFlag(locale: string): string {
ar: 'sa', // Arabic -> Saudi Arabia ar: 'sa', // Arabic -> Saudi Arabia
bn: 'bd', // Bengali -> Bangladesh bn: 'bd', // Bengali -> Bangladesh
ca: 'es', // Catalan -> Spain ca: 'es', // Catalan -> Spain
cs: 'cz', // Czech -> Czech Republic (note: modern country code is actually 'cz')
ckb: 'iq', // Central Kurdish -> Iraq ckb: 'iq', // Central Kurdish -> Iraq
cs: 'cz', // Czech -> Czech Republic (note: modern country code is actually 'cz')
da: 'dk', // Danish -> Denmark da: 'dk', // Danish -> Denmark
el: 'gr', // Greek -> Greece el: 'gr', // Greek -> Greece
et: 'ee', // Estonian -> Estonia
en: 'gb', // English -> Great Britain en: 'gb', // English -> Great Britain
et: 'ee', // Estonian -> Estonia
he: 'il', // Hebrew -> Israel he: 'il', // Hebrew -> Israel
hi: 'in', // Hindi -> India hi: 'in', // Hindi -> India
hy: 'am', // Armenian -> Armenia
ja: 'jp', // Japanese -> Japan ja: 'jp', // Japanese -> Japan
km: 'kh', // Khmer -> Cambodia km: 'kh', // Khmer -> Cambodia
ko: 'kr', // Korean -> South Korea ko: 'kr', // Korean -> South Korea
@@ -27,8 +28,7 @@ function getEmojiFlag(locale: string): string {
sv: 'se', // Swedish -> Sweden sv: 'se', // Swedish -> Sweden
uk: 'ua', // Ukrainian -> Ukraine uk: 'ua', // Ukrainian -> Ukraine
ur: 'pk', // Urdu -> Pakistan ur: 'pk', // Urdu -> Pakistan
vi: 'vn', // Vietnamese -> Vietnam vi: 'vn' // Vietnamese -> Vietnam
hy: 'am' // Armenian -> Armenia
} }
const baseLanguage = locale.split('-')[0]?.toLowerCase() || locale const baseLanguage = locale.split('-')[0]?.toLowerCase() || locale

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -111,7 +111,7 @@ function setBlackAsPrimary(value: boolean) {
v-for="color in neutralColors" v-for="color in neutralColors"
:key="color" :key="color"
:label="color" :label="color"
:chip="color" :chip="color === 'neutral' ? 'old-neutral' : color"
:selected="neutral === color" :selected="neutral === color"
@click="neutral = color" @click="neutral = color"
/> />

View File

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

View File

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

View File

@@ -15,6 +15,7 @@ const { data: files } = useLazyAsyncData('search', () => queryCollectionSearchSe
}) })
const links = useLinks() const links = useLinks()
const searchLinks = useSearchLinks()
const color = computed(() => colorMode.value === 'dark' ? (colors as any)[appConfig.ui.colors.neutral][900] : 'white') const color = computed(() => colorMode.value === 'dark' ? (colors as any)[appConfig.ui.colors.neutral][900] : 'white')
const radius = computed(() => `:root { --ui-radius: ${appConfig.theme.radius}rem; }`) const radius = computed(() => `:root { --ui-radius: ${appConfig.theme.radius}rem; }`)
const blackAsPrimary = computed(() => appConfig.theme.blackAsPrimary ? `:root { --ui-primary: black; } .dark { --ui-primary: white; }` : ':root {}') const blackAsPrimary = computed(() => appConfig.theme.blackAsPrimary ? `:root { --ui-primary: black; } .dark { --ui-primary: white; }` : ':root {}')
@@ -66,6 +67,7 @@ provide('navigation', mappedNavigation)
<ClientOnly> <ClientOnly>
<LazyUContentSearch <LazyUContentSearch
:links="searchLinks"
:files="files" :files="files"
:groups="[{ :groups="[{
id: 'framework', id: 'framework',

View File

@@ -176,7 +176,11 @@ community:
links: links:
- label: Star on GitHub - label: Star on GitHub
color: neutral color: neutral
variant: outline
to: https://github.com/nuxt/ui to: https://github.com/nuxt/ui
target: _blank target: _blank
icon: i-lucide-star icon: i-lucide-star
- label: Meet the team
color: neutral
variant: outline
to: /team
trailingIcon: i-lucide-arrow-right

View File

@@ -1,7 +1,8 @@
<script setup lang="ts"> <script setup lang="ts">
import { kebabCase } from 'scule' import { kebabCase } from 'scule'
import type { ContentNavigationItem } from '@nuxt/content' import type { ContentNavigationItem } from '@nuxt/content'
import { findPageBreadcrumb, mapContentNavigation } from '#ui-pro/utils/content' import type { PageLink } from '@nuxt/ui-pro'
import { findPageBreadcrumb, mapContentNavigation } from '@nuxt/ui-pro/utils/content'
const route = useRoute() const route = useRoute()
const { framework, module } = useSharedData() const { framework, module } = useSharedData()
@@ -66,9 +67,9 @@ if (!import.meta.prerender) {
const type = page.value?.path.includes('components') ? 'Vue Component ' : page.value?.path.includes('composables') ? 'Vue Composable ' : '' const type = page.value?.path.includes('components') ? 'Vue Component ' : page.value?.path.includes('composables') ? 'Vue Composable ' : ''
useSeoMeta({ useSeoMeta({
titleTemplate: `%s ${type}- Nuxt UI ${page.value.module === 'ui-pro' ? 'Pro' : ''} v3${page.value.framework === 'vue' ? ' for Vue' : ''}`, titleTemplate: `%s ${type}- Nuxt UI ${page.value.module === 'ui-pro' ? 'Pro' : ''} ${page.value.framework === 'vue' ? ' for Vue' : ''}`,
title: page.value.navigation?.title ? page.value.navigation.title : page.value.title, title: page.value.navigation?.title ? page.value.navigation.title : page.value.title,
ogTitle: `${page.value.navigation?.title ? page.value.navigation.title : page.value.title} ${type}- Nuxt UI ${page.value.module === 'ui-pro' ? 'Pro' : ''} v3${page.value.framework === 'vue' ? ' for Vue' : ''}`, ogTitle: `${page.value.navigation?.title ? page.value.navigation.title : page.value.title} ${type}- Nuxt UI ${page.value.module === 'ui-pro' ? 'Pro' : ''} ${page.value.framework === 'vue' ? ' for Vue' : ''}`,
description: page.value.description, description: page.value.description,
ogDescription: page.value.description ogDescription: page.value.description
}) })
@@ -100,15 +101,25 @@ const communityLinks = computed(() => [{
label: 'Star on GitHub', label: 'Star on GitHub',
to: `https://github.com/nuxt/${page.value?.module === 'ui-pro' ? 'ui-pro' : 'ui'}`, to: `https://github.com/nuxt/${page.value?.module === 'ui-pro' ? 'ui-pro' : 'ui'}`,
target: '_blank' target: '_blank'
}, module.value === 'ui-pro' && {
icon: 'i-lucide-credit-card',
label: 'Purchase a license',
to: 'https://nuxt.lemonsqueezy.com/checkout/buy/057dacb2-87ba-4dc1-9256-59ee5b3bd394',
target: '_blank'
}, module.value === 'ui-pro' && {
icon: 'i-lucide-ticket-percent',
label: 'Become an affiliate',
to: 'https://nuxt.lemonsqueezy.com/affiliates',
target: '_blank'
}, { }, {
icon: 'i-lucide-life-buoy', icon: 'i-lucide-git-pull-request-arrow',
label: 'Contribution', label: 'Contribution',
to: '/getting-started/contribution' to: '/getting-started/contribution'
}, { }, {
label: 'Roadmap', label: 'Roadmap',
icon: 'i-lucide-map', icon: 'i-lucide-map',
to: '/roadmap' to: '/roadmap'
}]) }].filter(Boolean) as PageLink[])
</script> </script>
<template> <template>
@@ -136,7 +147,7 @@ const communityLinks = computed(() => [{
v-bind="link" v-bind="link"
> >
<template v-if="link.avatar" #leading> <template v-if="link.avatar" #leading>
<UAvatar v-bind="link.avatar" size="2xs" /> <UAvatar v-bind="link.avatar" size="2xs" :alt="`${link.label} avatar`" />
</template> </template>
</UButton> </UButton>
</template> </template>

View File

@@ -7,7 +7,7 @@ const title = 'Vue Components'
const description = 'Explore 99+ customizable UI components for Vue and Nuxt built with Tailwind CSS and Reka UI.' const description = 'Explore 99+ customizable UI components for Vue and Nuxt built with Tailwind CSS and Reka UI.'
useSeoMeta({ useSeoMeta({
titleTemplate: `%s - Nuxt UI`, titleTemplate: '%s - Nuxt UI',
title, title,
description, description,
ogTitle: `${title} - Nuxt UI`, ogTitle: `${title} - Nuxt UI`,
@@ -169,6 +169,7 @@ onMounted(() => {
:loading="index >= 4 ? 'lazy' : 'eager'" :loading="index >= 4 ? 'lazy' : 'eager'"
width="640" width="640"
height="360" height="360"
:alt="`${component.name} preview`"
/> />
</div> </div>
</UPageCard> </UPageCard>

View File

@@ -5,6 +5,7 @@ import { animate } from 'motion-v'
import { joinURL } from 'ufo' import { joinURL } from 'ufo'
const { url } = useSiteConfig() const { url } = useSiteConfig()
useSeoMeta({ useSeoMeta({
title: page.title, title: page.title,
description: page.description, description: page.description,
@@ -155,7 +156,7 @@ onMounted(async () => {
:src="item.src" :src="item.src"
:alt="item.alt" :alt="item.alt"
class="w-full h-auto rounded-[calc(var(--ui-radius)*2)]" class="w-full h-auto rounded-[calc(var(--ui-radius)*2)]"
lazy loading="lazy"
/> />
</template> </template>
</UTabs> </UTabs>
@@ -165,7 +166,7 @@ onMounted(async () => {
v-if="page.section2.image" v-if="page.section2.image"
v-bind="page.section2.image" v-bind="page.section2.image"
class="w-full h-auto rounded-[calc(var(--ui-radius)*2)]" class="w-full h-auto rounded-[calc(var(--ui-radius)*2)]"
lazy loading="lazy"
/> />
</UPageSection> </UPageSection>
<UPageSection v-bind="page.section3" orientation="horizontal" :ui="{ container: 'py-16 sm:pt-16 lg:pt-16' }"> <UPageSection v-bind="page.section3" orientation="horizontal" :ui="{ container: 'py-16 sm:pt-16 lg:pt-16' }">
@@ -173,7 +174,7 @@ onMounted(async () => {
v-if="page.section3.image" v-if="page.section3.image"
v-bind="page.section3.image" v-bind="page.section3.image"
class="w-full h-auto rounded-[calc(var(--ui-radius)*2)]" class="w-full h-auto rounded-[calc(var(--ui-radius)*2)]"
lazy loading="lazy"
/> />
</UPageSection> </UPageSection>
<USeparator /> <USeparator />
@@ -198,7 +199,7 @@ onMounted(async () => {
v-if="step.image" v-if="step.image"
v-bind="step.image" v-bind="step.image"
class="rounded-(--ui-radius)" class="rounded-(--ui-radius)"
lazy loading="lazy"
/> />
<div> <div>
<h2 class="font-semibold inline-flex items-center gap-x-1"> <h2 class="font-semibold inline-flex items-center gap-x-1">
@@ -272,6 +273,7 @@ onMounted(async () => {
:key="index" :key="index"
v-bind="logo" v-bind="logo"
class="h-6 shrink-0 max-w-[140px] filter invert dark:invert-0" class="h-6 shrink-0 max-w-[140px] filter invert dark:invert-0"
loading="lazy"
> >
</UPageMarquee> </UPageMarquee>
</UPageCTA> </UPageCTA>

View File

@@ -23,18 +23,7 @@ const { data: components } = await useAsyncData('ui-components', () => {
.all() .all()
}) })
const { data: module } = await useFetch<{ const { data: module } = await useFetch('/api/module.json')
stats: {
downloads: number
stars: number
}
contributors: {
username: string
}[]
}>('https://api.nuxt.com/modules/ui', {
key: 'stats',
transform: ({ stats, contributors }) => ({ stats, contributors })
})
const { format } = Intl.NumberFormat('en', { notation: 'compact' }) const { format } = Intl.NumberFormat('en', { notation: 'compact' })
@@ -85,7 +74,7 @@ useIntersectionObserver(contributorsRef, ([entry]) => {
</div> </div>
</template> </template>
<LazySkyBg /> <LazySkyBg is-index />
<div class="h-[344px] lg:h-full lg:relative w-full lg:min-h-[calc(100vh-var(--ui-header-height)-1px)] overflow-hidden"> <div class="h-[344px] lg:h-full lg:relative w-full lg:min-h-[calc(100vh-var(--ui-header-height)-1px)] overflow-hidden">
<UPageMarquee <UPageMarquee
@@ -103,10 +92,14 @@ useIntersectionObserver(contributorsRef, ([entry]) => {
:to="component.path" :to="component.path"
> >
<UColorModeImage <UColorModeImage
:light="`${component.path.replace('/components/', '/components/light/')}.png`" :light="`${component.path.replace('/components/', '/components/light/')}.png`"
:dark="`${component.path.replace('/components/', '/components/dark/')}.png`" :dark="`${component.path.replace('/components/', '/components/dark/')}.png`"
:alt="`${component.title} preview`"
width="290"
height="163"
format="webp"
class="hover:scale-105 lg:hover:scale-110 transition-transform aspect-video w-full border-x lg:border-x-0 lg:border-y border-(--ui-border) 2xl:border-y-0" class="hover:scale-105 lg:hover:scale-110 transition-transform aspect-video w-full border-x lg:border-x-0 lg:border-y border-(--ui-border) 2xl:border-y-0"
loading="lazy"
/> />
<UBadge color="neutral" variant="outline" size="md" :label="component.title" class="hidden lg:block absolute mx-auto top-4 left-6 xl:left-4 group-hover/link:opacity-100 opacity-0 transition-all duration-300 pointer-events-none -translate-y-2 group-hover/link:translate-y-0" /> <UBadge color="neutral" variant="outline" size="md" :label="component.title" class="hidden lg:block absolute mx-auto top-4 left-6 xl:left-4 group-hover/link:opacity-100 opacity-0 transition-all duration-300 pointer-events-none -translate-y-2 group-hover/link:translate-y-0" />
</ULink> </ULink>
@@ -130,7 +123,12 @@ useIntersectionObserver(contributorsRef, ([entry]) => {
<UColorModeImage <UColorModeImage
:light="`${component.path.replace('/components/', '/components/light/')}.png`" :light="`${component.path.replace('/components/', '/components/light/')}.png`"
:dark="`${component.path.replace('/components/', '/components/dark/')}.png`" :dark="`${component.path.replace('/components/', '/components/dark/')}.png`"
:alt="`${component.title} preview`"
width="290"
height="163"
format="webp"
class="hover:scale-105 lg:hover:scale-110 transition-transform aspect-video w-full border-x lg:border-x-0 lg:border-y border-(--ui-border) 2xl:border-y-0" class="hover:scale-105 lg:hover:scale-110 transition-transform aspect-video w-full border-x lg:border-x-0 lg:border-y border-(--ui-border) 2xl:border-y-0"
loading="lazy"
/> />
<UBadge color="neutral" variant="outline" size="md" :label="component.title" class="hidden lg:block absolute mx-auto top-4 left-6 xl:left-4 group-hover/link:opacity-100 opacity-0 transition-all duration-300 pointer-events-none -translate-y-2 group-hover/link:translate-y-0" /> <UBadge color="neutral" variant="outline" size="md" :label="component.title" class="hidden lg:block absolute mx-auto top-4 left-6 xl:left-4 group-hover/link:opacity-100 opacity-0 transition-all duration-300 pointer-events-none -translate-y-2 group-hover/link:translate-y-0" />
</ULink> </ULink>
@@ -152,7 +150,9 @@ useIntersectionObserver(contributorsRef, ([entry]) => {
:in-view-options="{ once: true }" :in-view-options="{ once: true }"
class="flex items-start gap-x-3 relative group" class="flex items-start gap-x-3 relative group"
> >
<NuxtLink v-if="feature.to" :to="feature.to" class="absolute inset-0 z-10" /> <NuxtLink v-if="feature.to" :to="feature.to" class="absolute inset-0 z-10">
<span class="sr-only">Go to {{ feature.title }}</span>
</NuxtLink>
<div class="relative p-3"> <div class="relative p-3">
<svg class="absolute inset-0" viewBox="0 0 44 44" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg class="absolute inset-0" viewBox="0 0 44 44" fill="none" xmlns="http://www.w3.org/2000/svg">
@@ -165,12 +165,12 @@ useIntersectionObserver(contributorsRef, ([entry]) => {
<circle cx="6.53711" cy="37.4551" r="1.5" fill="var(--ui-border-accented)" /> <circle cx="6.53711" cy="37.4551" r="1.5" fill="var(--ui-border-accented)" />
<circle cx="38.5957" cy="37.4551" r="1.5" fill="var(--ui-border-accented)" /> <circle cx="38.5957" cy="37.4551" r="1.5" fill="var(--ui-border-accented)" />
</svg> </svg>
<UIcon :name="feature.icon" class="size-5 flex-shrink-0" /> <UIcon :name="feature.icon" class="size-5 shrink-0" />
</div> </div>
<div class="flex flex-col"> <div class="flex flex-col">
<h2 class="font-medium text-(--ui-text-highlighted) inline-flex items-center gap-x-1"> <h2 class="font-medium text-(--ui-text-highlighted) inline-flex items-center gap-x-1">
{{ feature.title }} {{ feature.title }}
<UIcon v-if="feature.to" name="i-lucide-arrow-right" class="size-4 flex-shrink-0 opacity-0 group-hover:opacity-100 transition-all duration-200 -translate-x-1 group-hover:translate-x-0" /> <UIcon v-if="feature.to" name="i-lucide-arrow-right" class="size-4 shrink-0 opacity-0 group-hover:opacity-100 transition-all duration-200 -translate-x-1 group-hover:translate-x-0" />
</h2> </h2>
<p class="text-sm text-(--ui-text-muted)"> <p class="text-sm text-(--ui-text-muted)">
{{ feature.description }} {{ feature.description }}
@@ -218,26 +218,32 @@ useIntersectionObserver(contributorsRef, ([entry]) => {
class="border-b border-(--ui-border)" class="border-b border-(--ui-border)"
> >
<template #features> <template #features>
<NuxtLink to="https://npm.chart.dev/@nuxt/ui" target="_blank" class="min-w-0"> <li>
<p class="text-4xl font-semibold text-(--ui-text-highlighted) truncate"> <NuxtLink to="https://npm.chart.dev/@nuxt/ui" target="_blank" class="min-w-0">
{{ format(module?.stats?.downloads ?? 0) }}+ <p class="text-4xl font-semibold text-(--ui-text-highlighted) truncate">
</p> {{ format(module?.stats?.downloads ?? 0) }}+
<p class="text-(--ui-text-muted) text-sm truncate">monthly downloads</p> </p>
</NuxtLink> <p class="text-(--ui-text-muted) text-sm truncate">monthly downloads</p>
</NuxtLink>
</li>
<NuxtLink to="https://github.com/nuxt/ui" target="_blank" class="min-w-0"> <li>
<p class="text-4xl font-semibold text-(--ui-text-highlighted) truncate"> <NuxtLink to="https://github.com/nuxt/ui" target="_blank" class="min-w-0">
{{ format(module?.stats?.stars ?? 0) }}+ <p class="text-4xl font-semibold text-(--ui-text-highlighted) truncate">
</p> {{ format(module?.stats?.stars ?? 0) }}+
<p class="text-(--ui-text-muted) text-sm truncate">GitHub stars</p> </p>
</NuxtLink> <p class="text-(--ui-text-muted) text-sm truncate">GitHub stars</p>
</NuxtLink>
</li>
<NuxtLink to="https://github.com/nuxt/ui/graphs/contributors" target="_blank" class="min-w-0"> <li>
<p class="text-4xl font-semibold text-(--ui-text-highlighted) truncate"> <NuxtLink to="https://github.com/nuxt/ui/graphs/contributors" target="_blank" class="min-w-0">
175+ <p class="text-4xl font-semibold text-(--ui-text-highlighted) truncate">
</p> 175+
<p class="text-(--ui-text-muted) text-sm truncate">Contributors</p> </p>
</NuxtLink> <p class="text-(--ui-text-muted) text-sm truncate">Contributors</p>
</NuxtLink>
</li>
</template> </template>
<div ref="contributorsRef" class="p-4 sm:px-6 md:px-8 lg:px-12 xl:px-14 overflow-hidden flex relative"> <div ref="contributorsRef" class="p-4 sm:px-6 md:px-8 lg:px-12 xl:px-14 overflow-hidden flex relative">
@@ -296,8 +302,8 @@ useIntersectionObserver(contributorsRef, ([entry]) => {
:src="`/pro/blocks/image${i}.png`" :src="`/pro/blocks/image${i}.png`"
width="460" width="460"
height="258" height="258"
:alt="`Nuxt UI Pro Screenshot ${i}`"
loading="lazy" loading="lazy"
:alt="`Nuxt UI Pro Screenshot ${i}`"
class="aspect-video border border-(--ui-border) rounded-[calc(var(--ui-radius)*2)] bg-white" class="aspect-video border border-(--ui-border) rounded-[calc(var(--ui-radius)*2)] bg-white"
> >
</UPageMarquee> </UPageMarquee>

View File

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

View File

@@ -9,10 +9,10 @@ const { url } = useSiteConfig()
useSeoMeta({ useSeoMeta({
title: page.title, title: page.title,
ogTitle: page.title,
ogImage: joinURL(url, '/pro/og-image.png'),
description: page.description, description: page.description,
ogDescription: page.description ogTitle: page.title,
ogDescription: page.description,
ogImage: joinURL(url, '/pro/og-image.png')
}) })
</script> </script>

View File

@@ -7,8 +7,8 @@ const { url } = useSiteConfig()
useSeoMeta({ useSeoMeta({
title: page.title, title: page.title,
ogTitle: page.title,
description: page.description, description: page.description,
ogTitle: page.title,
ogDescription: page.description, ogDescription: page.description,
ogImage: joinURL(url, '/pro/og-image.png') ogImage: joinURL(url, '/pro/og-image.png')
}) })
@@ -81,6 +81,7 @@ useSeoMeta({
:key="index" :key="index"
v-bind="logo" v-bind="logo"
class="h-6 shrink-0 max-w-[140px] filter invert dark:invert-0" class="h-6 shrink-0 max-w-[140px] filter invert dark:invert-0"
loading="lazy"
> >
</UPageMarquee> </UPageMarquee>
<UContainer> <UContainer>

View File

@@ -56,6 +56,7 @@ useSeoMeta({
v-if="template.thumbnail" v-if="template.thumbnail"
v-bind="template.thumbnail" v-bind="template.thumbnail"
class="w-full h-auto border lg:border-y lg:border-x-0 border-(--ui-border) rounded-(--ui-radius) lg:rounded-none" class="w-full h-auto border lg:border-y lg:border-x-0 border-(--ui-border) rounded-(--ui-radius) lg:rounded-none"
:alt="`Template ${index} thumbnail`"
width="656" width="656"
height="369" height="369"
loading="lazy" loading="lazy"
@@ -66,7 +67,7 @@ useSeoMeta({
:items="(template.images as any[])" :items="(template.images as any[])"
dots dots
> >
<NuxtImg v-bind="item" class="w-full h-full object-cover" width="576" height="360" /> <NuxtImg v-bind="item" class="w-full h-full object-cover" width="576" height="360" loading="lazy" />
</UCarousel> </UCarousel>
<Placeholder v-else class="w-full h-full aspect-video" /> <Placeholder v-else class="w-full h-full aspect-video" />
</Motion> </Motion>

View File

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

View File

@@ -5,8 +5,9 @@ const description = 'Discover our Volta board for @nuxt/ui development status.'
useSeoMeta({ useSeoMeta({
titleTemplate: '%s - Nuxt UI', titleTemplate: '%s - Nuxt UI',
title, title,
ogTitle: 'Nuxt UI Roadmap', description,
description ogTitle: `${title} - Nuxt UI`,
ogDescription: description
}) })
defineOgImageComponent('Docs', { defineOgImageComponent('Docs', {

View File

@@ -0,0 +1,83 @@
<script setup lang="ts">
const { data: page } = await useAsyncData('showcase', () => queryCollection('showcase').first())
if (!page.value) {
throw createError({ statusCode: 404, statusMessage: 'Page not found', fatal: true })
}
useSeoMeta({
titleTemplate: `%s - Nuxt UI`,
title: page.value.title,
description: page.value.description,
ogTitle: `${page.value.title} - Nuxt UI`,
ogDescription: page.value.description
})
defineOgImageComponent('Docs', {
headline: 'Community'
})
</script>
<template>
<UMain v-if="page">
<UPageHero
:title="page.hero.title"
:description="page.hero.description"
:links="page.hero.links"
:ui="{
wrapper: 'lg:px-12',
container: 'relative'
}"
>
<template #top>
<div class="absolute z-[-1] rounded-full bg-(--ui-primary) blur-[300px] size-60 sm:size-80 transform -translate-x-1/2 left-1/2 -translate-y-80" />
</template>
<LazyStarsBg />
<div aria-hidden="true" class="hidden lg:block absolute z-[-1] border-x border-(--ui-border) inset-0 mx-4 sm:mx-6 lg:mx-8" />
<div class="border-l border-t border-(--ui-border)">
<ul class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 items-start justify-center divide-y divide-x divide-(--ui-border)">
<li
v-for="item in page.items"
:key="item.name"
class="group relative flex items-center justify-center flex-1 size-full p-2 last:border-r last:border-b border-(--ui-border) overflow-hidden"
>
<NuxtLink class="inset-0 absolute" :to="item.url" target="_blank">
<span class="sr-only">Go to {{ item.name }}</span>
</NuxtLink>
<NuxtImg
:src="`/assets/showcase/${item.name.toLowerCase().replace(/\s/g, '-')}.png`"
:alt="`Screenshot of ${item.name}`"
width="327"
height="184"
:modifiers="{
position: 'top'
}"
class="aspect-[16/9] size-full opacity-75 group-hover:opacity-100 group-hover:scale-110 duration-200 transition-[scale,opacity] pointer-events-none"
/>
<div class="absolute flex items-center px-2.5 py-0.75 gap-1 opacity-0 group-hover:opacity-100 transition-opacity duration-200 pointer-events-none bg-black/90 rounded-full">
<span class="text-sm text-white font-medium">
{{ item.name }}
</span>
<UIcon name="i-lucide-arrow-up-right" class="size-4 shrink-0 text-white" />
</div>
</li>
</ul>
</div>
<div class="flex justify-center -mb-[36px]">
<UButton
label="Submit your project"
trailing-icon="i-lucide-plus"
color="neutral"
size="lg"
to="https://github.com/nuxt/ui/edit/v3/docs/content/showcase.yml"
target="_blank"
/>
</div>
</UPageHero>
</UMain>
</template>

156
docs/app/pages/team.vue Normal file
View File

@@ -0,0 +1,156 @@
<script setup lang="ts">
const title = 'Meet the Team'
const description = 'The development of Nuxt UI is led by a community of developers from all over the world.'
useSeoMeta({
titleTemplate: '%s - Nuxt UI',
title,
description,
ogTitle: `${title} - Nuxt UI`,
ogDescription: description
})
defineOgImageComponent('Docs', {
headline: 'Community'
})
const { data: module } = await useFetch('/api/module.json')
const contributors = computed(() => module.value?.contributors?.filter(contributor => !module.value?.team?.find(user => user.login === contributor.username)))
const icons = {
website: 'i-lucide-link',
twitter: 'i-simple-icons-x',
twitch: 'i-simple-icons-twitch',
youtube: 'i-simple-icons-youtube',
instagram: 'i-simple-icons-instagram',
linkedin: 'i-simple-icons-linkedin',
mastodon: 'i-simple-icons-mastodon',
bluesky: 'i-simple-icons-bluesky',
github: 'i-simple-icons-github'
}
</script>
<template>
<UMain>
<UPageHero
:title="title"
:description="description"
class="relative"
orientation="vertical"
:ui="{ title: 'text-balance', container: 'relative' }"
>
<template #top>
<div class="absolute z-[-1] rounded-full bg-(--ui-primary) blur-[300px] size-60 sm:size-80 transform -translate-x-1/2 left-1/2 -translate-y-80" />
</template>
<LazyStarsBg />
</UPageHero>
<UPageSection :ui="{ container: '!pt-0' }">
<UPageGrid class="xl:grid-cols-4">
<UPageCard
v-for="(user, index) in module?.team"
:key="index"
:title="user.name"
:description="[user.pronouns, user.location].filter(Boolean).join(' ・ ')"
:ui="{
container: 'gap-y-4 lg:p-8',
leading: 'flex justify-center',
title: 'text-center',
description: 'text-center text-(--ui-text-muted)'
}"
variant="subtle"
>
<template #leading>
<UAvatar
:src="`https://ipx.nuxt.com/f_auto,s_80x80/gh_avatar/${user.login}`"
:srcset="`https://ipx.nuxt.com/f_auto,s_160x160/gh_avatar/${user.login} 2x`"
size="3xl"
class="mx-auto"
/>
</template>
<div class="flex items-center justify-center gap-1">
<UButton
v-for="(link, key) in user.socialAccounts"
:key="key"
color="neutral"
variant="link"
:to="link.url"
:icon="icons[key as keyof typeof icons] || icons.website"
:alt="`Link to ${user.name}'s ${key} profile`"
target="_blank"
size="sm"
/>
<UButton
:to="`https://github.com/${user.login}`"
color="neutral"
variant="link"
:alt="`Link to ${user.name}'s GitHub profile`"
:icon="icons.github"
target="_blank"
/>
<UButton
v-if="user.websiteUrl"
:to="user.websiteUrl"
color="neutral"
variant="link"
:alt="`Link to ${user.name}'s personal website`"
:icon="icons.website"
target="_blank"
/>
</div>
<div v-if="user.sponsorsListing" class="flex items-center justify-center">
<UButton
:to="user.sponsorsListing"
target="_blank"
color="neutral"
variant="subtle"
icon="i-lucide-heart"
label="Sponsor"
:ui="{ leadingIcon: 'text-pink-500 dark:text-pink-400' }"
/>
</div>
</UPageCard>
</UPageGrid>
<ProseHr />
<UPageGrid class="xl:grid-cols-6">
<UPageCard
v-for="contributor in contributors"
:key="contributor.username"
:title="contributor.username"
:ui="{
container: 'gap-y-2',
leading: 'flex justify-center',
title: 'text-center',
description: 'text-center text-(--ui-text-muted)'
}"
>
<template #leading>
<UAvatar
:src="`https://ipx.nuxt.com/f_auto,s_80x80/gh_avatar/${contributor.username}`"
:srcset="`https://ipx.nuxt.com/f_auto,s_160x160/gh_avatar/${contributor.username} 2x`"
size="3xl"
class="mx-auto"
loading="lazy"
/>
</template>
<div class="flex items-center justify-center gap-1">
<UButton
:to="`https://github.com/${contributor.username}`"
color="neutral"
variant="link"
:alt="`Link to ${contributor.username}'s GitHub profile`"
:icon="icons.github"
target="_blank"
/>
</div>
</UPageCard>
</UPageGrid>
</UPageSection>
</UMain>
</template>

View File

@@ -1,6 +1,18 @@
import { defineCollection, z } from '@nuxt/content' import { defineCollection, z } from '@nuxt/content'
import { resolve } from 'node:path' import { resolve } from 'node:path'
const Button = z.object({
label: z.string(),
icon: z.string().optional(),
trailingIcon: z.string().optional(),
to: z.string().optional(),
color: z.enum(['primary', 'neutral', 'success', 'warning', 'error', 'info']).optional(),
size: z.enum(['xs', 'sm', 'md', 'lg', 'xl']).optional(),
variant: z.enum(['solid', 'outline', 'subtle', 'soft', 'ghost', 'link']).optional(),
id: z.string().optional(),
target: z.enum(['_blank', '_self']).optional()
})
const schema = z.object({ const schema = z.object({
category: z.enum(['layout', 'form', 'element', 'navigation', 'data', 'overlay']).optional(), category: z.enum(['layout', 'form', 'element', 'navigation', 'data', 'overlay']).optional(),
framework: z.string().optional(), framework: z.string().optional(),
@@ -42,5 +54,26 @@ export const collections = {
include: '**/*' include: '**/*'
}, pro!].filter(Boolean), }, pro!].filter(Boolean),
schema schema
}),
showcase: defineCollection({
type: 'page',
source: 'showcase.yml',
schema: z.object({
title: z.string(),
description: z.string(),
hero: z.object({
title: z.string(),
description: z.string(),
links: z.array(Button)
}),
items: z.array(z.object({
name: z.string(),
url: z.string(),
screenshotUrl: z.string().optional(),
screenshotOptions: z.object({
delay: z.number()
})
}))
})
}) })
} }

View File

@@ -6,7 +6,7 @@ navigation.icon: i-lucide-house
<iframe width="100%" height="100%" src="https://www.youtube-nocookie.com/embed/_eQxomah-nA?si=pDSzchUBDKb2NQu7" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen style="aspect-ratio: 16/9;" class="rounded-[calc(var(--ui-radius)*1.5)]"></iframe> <iframe width="100%" height="100%" src="https://www.youtube-nocookie.com/embed/_eQxomah-nA?si=pDSzchUBDKb2NQu7" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen style="aspect-ratio: 16/9;" class="rounded-[calc(var(--ui-radius)*1.5)]"></iframe>
### Reka UI ## Reka UI
We've transitioned from [Headless UI](https://headlessui.com/) to [Reka UI](https://reka-ui.com/) as our core component foundation. This shift brings several key advantages: We've transitioned from [Headless UI](https://headlessui.com/) to [Reka UI](https://reka-ui.com/) as our core component foundation. This shift brings several key advantages:
@@ -17,7 +17,7 @@ We've transitioned from [Headless UI](https://headlessui.com/) to [Reka UI](http
This transition empowers Nuxt UI to become a more comprehensive and flexible UI library, offering developers greater power and customization options. This transition empowers Nuxt UI to become a more comprehensive and flexible UI library, offering developers greater power and customization options.
### Tailwind CSS v4 ## Tailwind CSS v4
Nuxt UI integrates the latest Tailwind CSS v4, bringing significant improvements: Nuxt UI integrates the latest Tailwind CSS v4, bringing significant improvements:
@@ -30,7 +30,7 @@ Nuxt UI integrates the latest Tailwind CSS v4, bringing significant improvements
Learn about all the breaking changes in Tailwind CSS v4. Learn about all the breaking changes in Tailwind CSS v4.
:: ::
### Tailwind Variants ## Tailwind Variants
We've adopted [Tailwind Variants](https://www.tailwind-variants.org/) to manage our design system, offering: We've adopted [Tailwind Variants](https://www.tailwind-variants.org/) to manage our design system, offering:
@@ -40,7 +40,7 @@ We've adopted [Tailwind Variants](https://www.tailwind-variants.org/) to manage
This integration unifies the styling of components, ensuring consistency and code maintainability. This integration unifies the styling of components, ensuring consistency and code maintainability.
### TypeScript Integration ## TypeScript Integration
Nuxt UI offers significantly improved TypeScript integration, providing a superior developer experience: Nuxt UI offers significantly improved TypeScript integration, providing a superior developer experience:
@@ -60,7 +60,7 @@ Nuxt UI offers significantly improved TypeScript integration, providing a superi
Check out an example of the Accordion component with auto-completion for props and slots. Check out an example of the Accordion component with auto-completion for props and slots.
:: ::
### Vue compatibility ## Vue compatibility
You can now use Nuxt UI in any Vue project without Nuxt by adding the Vite and Vue plugins to your configuration. This provides: You can now use Nuxt UI in any Vue project without Nuxt by adding the Vite and Vue plugins to your configuration. This provides:
@@ -72,7 +72,7 @@ You can now use Nuxt UI in any Vue project without Nuxt by adding the Vite and V
Learn how to install and configure Nuxt UI in a Vue project in the **Vue installation guide**. Learn how to install and configure Nuxt UI in a Vue project in the **Vue installation guide**.
:: ::
### Nuxt DevTools Integration ## Nuxt DevTools Integration
You can play with Nuxt UI components as well as your app components directly from Nuxt Devtools with the [compodium](https://github.com/romhml/compodium) module, providing a powerful development experience: You can play with Nuxt UI components as well as your app components directly from Nuxt Devtools with the [compodium](https://github.com/romhml/compodium) module, providing a powerful development experience:

View File

@@ -136,19 +136,19 @@ To dynamically switch between languages, you can use the [Nuxt I18n](https://i18
::code-group{sync="pm"} ::code-group{sync="pm"}
```bash [pnpm] ```bash [pnpm]
pnpm add @nuxtjs/i18n@next pnpm add @nuxtjs/i18n
``` ```
```bash [yarn] ```bash [yarn]
yarn add @nuxtjs/i18n@next yarn add @nuxtjs/i18n
``` ```
```bash [npm] ```bash [npm]
npm install @nuxtjs/i18n@next npm install @nuxtjs/i18n
``` ```
```bash [bun] ```bash [bun]
bun add @nuxtjs/i18n@next bun add @nuxtjs/i18n
``` ```
:: ::

View File

@@ -30,6 +30,8 @@ ignore:
- items - items
external: external:
- items - items
externalTypes:
- AccordionItem[]
hide: hide:
- class - class
props: props:
@@ -58,6 +60,8 @@ ignore:
- items - items
external: external:
- items - items
externalTypes:
- AccordionItem[]
hide: hide:
- class - class
props: props:
@@ -87,6 +91,8 @@ ignore:
- items - items
external: external:
- items - items
externalTypes:
- AccordionItem[]
hide: hide:
- class - class
props: props:
@@ -115,6 +121,8 @@ ignore:
- items - items
external: external:
- items - items
externalTypes:
- AccordionItem[]
hide: hide:
- class - class
props: props:
@@ -149,6 +157,8 @@ ignore:
- items - items
external: external:
- items - items
externalTypes:
- AccordionItem[]
hide: hide:
- class - class
props: props:
@@ -182,6 +192,8 @@ ignore:
- items - items
external: external:
- items - items
externalTypes:
- AccordionItem[]
hide: hide:
- class - class
props: props:
@@ -280,6 +292,18 @@ props:
--- ---
:: ::
### With drag and drop
Use the [`useSortable`](https://vueuse.org/integrations/useSortable/) composable from [`@vueuse/integrations`](https://vueuse.org/integrations/README.html) to enable drag and drop functionality on the accordion. This integration wraps [Sortable.js](https://sortablejs.github.io/Sortable/) to provide a seamless drag and drop experience.
The `useSortable` composable accepts various options, see the [Usage](https://vueuse.org/integrations/useSortable/#usage) for more examples.
::component-example
---
name: 'accordion-drag-and-drop-example'
---
::
## API ## API
### Props ### Props

View File

@@ -27,6 +27,8 @@ ignore:
- items - items
external: external:
- items - items
externalTypes:
- BreadcrumbItem[]
props: props:
items: items:
- label: 'Home' - label: 'Home'
@@ -54,6 +56,8 @@ ignore:
- items - items
external: external:
- items - items
externalTypes:
- BreadcrumbItem[]
props: props:
separatorIcon: 'i-lucide-arrow-right' separatorIcon: 'i-lucide-arrow-right'
items: items:

View File

@@ -44,6 +44,8 @@ ignore:
- ui.content - ui.content
external: external:
- items - items
externalTypes:
- ContextMenuItem[][]
props: props:
items: items:
- - label: Appearance - - label: Appearance
@@ -124,6 +126,8 @@ ignore:
- ui.content - ui.content
external: external:
- items - items
externalTypes:
- ContextMenuItem[]
props: props:
size: xl size: xl
items: items:
@@ -158,6 +162,8 @@ ignore:
- ui.content - ui.content
external: external:
- items - items
externalTypes:
- ContextMenuItem[]
props: props:
disabled: true disabled: true
items: items:

View File

@@ -306,6 +306,17 @@ name: 'drawer-dismissible-example'
In this example, the `header` slot is used to add a close button which is not done by default. In this example, the `header` slot is used to add a close button which is not done by default.
:: ::
### Responsive drawer
You can render a [Modal](/components/modal) component on desktop and a Drawer on mobile for example.
::component-example
---
prettier: true
name: 'drawer-responsive-example'
---
::
### With footer slot ### With footer slot
Use the `#footer` slot to add content after the Drawer's body. Use the `#footer` slot to add content after the Drawer's body.

View File

@@ -44,6 +44,8 @@ ignore:
- ui.content - ui.content
external: external:
- items - items
externalTypes:
- DropdownMenuItem[][]
props: props:
items: items:
- - label: Benjamin - - label: Benjamin
@@ -123,6 +125,8 @@ ignore:
- ui.content - ui.content
external: external:
- items - items
externalTypes:
- DropdownMenuItem[]
items: items:
content.align: content.align:
- start - start
@@ -169,6 +173,8 @@ ignore:
- ui.content - ui.content
external: external:
- items - items
externalTypes:
- DropdownMenuItem[]
props: props:
arrow: true arrow: true
items: items:
@@ -202,6 +208,8 @@ ignore:
- ui.content - ui.content
external: external:
- items - items
externalTypes:
- DropdownMenuItem[]
props: props:
size: xl size: xl
items: items:
@@ -244,6 +252,8 @@ ignore:
- ui.content - ui.content
external: external:
- items - items
externalTypes:
- DropdownMenuItem[]
props: props:
disabled: true disabled: true
items: items:
@@ -334,7 +344,9 @@ Inside the `defineShortcuts` composable, there is an `extractShortcuts` utility
```vue ```vue
<script setup lang="ts"> <script setup lang="ts">
const items = [{ import type { DropdownMenuItem } from '@nuxt/ui'
const items: DropdownMenuItem[] = [{
label: 'Invite users', label: 'Invite users',
icon: 'i-lucide-user-plus', icon: 'i-lucide-user-plus',
children: [{ children: [{

View File

@@ -39,6 +39,8 @@ ignore:
- class - class
external: external:
- items - items
externalTypes:
- NavigationMenuItem[]
props: props:
items: items:
- label: Guide - label: Guide
@@ -148,6 +150,8 @@ ignore:
- class - class
external: external:
- items - items
externalTypes:
- NavigationMenuItem[][]
props: props:
orientation: 'vertical' orientation: 'vertical'
items: items:
@@ -247,6 +251,8 @@ ignore:
- class - class
external: external:
- items - items
externalTypes:
- NavigationMenuItem[][]
props: props:
highlight: true highlight: true
highlightColor: 'primary' highlightColor: 'primary'
@@ -346,6 +352,8 @@ ignore:
- class - class
external: external:
- items - items
externalTypes:
- NavigationMenuItem[][]
props: props:
color: neutral color: neutral
items: items:
@@ -379,6 +387,8 @@ ignore:
- class - class
external: external:
- items - items
externalTypes:
- NavigationMenuItem[][]
props: props:
color: neutral color: neutral
variant: link variant: link
@@ -423,6 +433,8 @@ ignore:
- class - class
external: external:
- items - items
externalTypes:
- NavigationMenuItem[]
props: props:
trailingIcon: 'i-lucide-arrow-down' trailingIcon: 'i-lucide-arrow-down'
items: items:
@@ -519,6 +531,8 @@ ignore:
- class - class
external: external:
- items - items
externalTypes:
- NavigationMenuItem[]
props: props:
arrow: true arrow: true
items: items:
@@ -611,6 +625,8 @@ ignore:
- class - class
external: external:
- items - items
externalTypes:
- NavigationMenuItem[]
props: props:
arrow: true arrow: true
contentOrientation: 'vertical' contentOrientation: 'vertical'
@@ -682,6 +698,8 @@ ignore:
- class - class
external: external:
- items - items
externalTypes:
- NavigationMenuItem[]
props: props:
unmountOnHide: false unmountOnHide: false
items: items:

View File

@@ -177,6 +177,12 @@ props:
:component-emits :component-emits
When accessing the component via a template ref, you can use the following:
| Name | Type |
| ---- | ---- |
| `inputsRef`{lang="ts-type"} | `Ref<ComponentPublicInstance[]>`{lang="ts-type"} |
## Theme ## Theme
:component-theme :component-theme

View File

@@ -17,7 +17,7 @@ Use the `v-model` directive to control the value of the RadioGroup or the `defau
### Items ### Items
Use the `items` prop as an array of strings, numbers or booleans: Use the `items` prop as an array of strings or numbers:
::component-code ::component-code
--- ---
@@ -28,6 +28,9 @@ ignore:
external: external:
- items - items
- modelValue - modelValue
externalTypes:
- RadioGroupItem[]
- RadioGroupValue
props: props:
modelValue: 'System' modelValue: 'System'
items: items:
@@ -52,6 +55,9 @@ ignore:
external: external:
- items - items
- modelValue - modelValue
externalTypes:
- RadioGroupItem[]
- RadioGroupValue
props: props:
modelValue: 'system' modelValue: 'system'
items: items:
@@ -84,6 +90,9 @@ ignore:
external: external:
- items - items
- modelValue - modelValue
externalTypes:
- RadioGroupItem[]
- RadioGroupValue
props: props:
modelValue: 'light' modelValue: 'light'
valueKey: 'id' valueKey: 'id'
@@ -112,6 +121,8 @@ ignore:
- items - items
external: external:
- items - items
externalTypes:
- RadioGroupItem[]
props: props:
legend: 'Theme' legend: 'Theme'
defaultValue: 'System' defaultValue: 'System'
@@ -122,28 +133,6 @@ props:
--- ---
:: ::
### Orientation
Use the `orientation` prop to change the orientation of the RadioGroup. Defaults to `vertical`.
::component-code
---
prettier: true
ignore:
- defaultValue
- items
external:
- items
props:
orientation: 'horizontal'
defaultValue: 'System'
items:
- 'System'
- 'Light'
- 'Dark'
---
::
### Color ### Color
Use the `color` prop to change the color of the RadioGroup. Use the `color` prop to change the color of the RadioGroup.
@@ -156,6 +145,8 @@ ignore:
- items - items
external: external:
- items - items
externalTypes:
- RadioGroupItem[]
props: props:
color: neutral color: neutral
defaultValue: 'System' defaultValue: 'System'
@@ -166,6 +157,35 @@ props:
--- ---
:: ::
### Variant :badge{label="Not released" class="align-text-top"}
Use the `variant` prop to change the variant of the RadioGroup.
::component-code
---
prettier: true
ignore:
- defaultValue
- items
external:
- items
props:
color: 'primary'
variant: 'table'
defaultValue: 'pro'
items:
- label: 'Pro'
value: 'pro'
description: 'Tailored for indie hackers, freelancers and solo founders.'
- label: 'Startup'
value: 'startup'
description: 'Best suited for small teams, startups and agencies.'
- label: 'Enterprise'
value: 'enterprise'
description: 'Ideal for larger teams and organizations.'
---
::
### Size ### Size
Use the `size` prop to change the size of the RadioGroup. Use the `size` prop to change the size of the RadioGroup.
@@ -178,8 +198,61 @@ ignore:
- items - items
external: external:
- items - items
externalTypes:
- RadioGroupItem[]
props: props:
size: 'xl' size: 'xl'
variant: 'list'
defaultValue: 'System'
items:
- 'System'
- 'Light'
- 'Dark'
---
::
### Orientation
Use the `orientation` prop to change the orientation of the RadioGroup. Defaults to `vertical`.
::component-code
---
prettier: true
ignore:
- defaultValue
- items
external:
- items
externalTypes:
- RadioGroupItem[]
props:
orientation: 'horizontal'
variant: 'list'
defaultValue: 'System'
items:
- 'System'
- 'Light'
- 'Dark'
---
::
### Indicator :badge{label="Not released" class="align-text-top"}
Use the `indicator` prop to change the position or hide the indicator. Defaults to `start`.
::component-code
---
prettier: true
ignore:
- defaultValue
- items
external:
- items
externalTypes:
- RadioGroupItem[]
props:
indicator: 'end'
variant: 'card'
defaultValue: 'System' defaultValue: 'System'
items: items:
- 'System' - 'System'
@@ -200,6 +273,8 @@ ignore:
- items - items
external: external:
- items - items
externalTypes:
- RadioGroupItem[]
props: props:
disabled: true disabled: true
defaultValue: 'System' defaultValue: 'System'

View File

@@ -31,6 +31,8 @@ ignore:
- class - class
external: external:
- items - items
externalTypes:
- StepperItem[]
props: props:
items: items:
- title: 'Address' - title: 'Address'
@@ -61,6 +63,8 @@ ignore:
- class - class
external: external:
- items - items
externalTypes:
- StepperItem[]
props: props:
color: neutral color: neutral
items: items:
@@ -88,6 +92,8 @@ ignore:
- class - class
external: external:
- items - items
externalTypes:
- StepperItem[]
props: props:
size: xl size: xl
items: items:
@@ -115,6 +121,8 @@ ignore:
- class - class
external: external:
- items - items
externalTypes:
- StepperItem[]
props: props:
orientation: vertical orientation: vertical
items: items:
@@ -142,6 +150,8 @@ ignore:
- class - class
external: external:
- items - items
externalTypes:
- StepperItem[]
props: props:
disabled: true disabled: true
items: items:

View File

@@ -23,7 +23,7 @@ class: '!p-0'
--- ---
:: ::
::callout{icon="i-simple-icons-github" to="https://github.com/nuxt/ui/tree/v3/docs/app/components/content/examples/table/TableExample.vue"} ::callout{icon="i-simple-icons-github" to="https://github.com/nuxt/ui/tree/v3/docs/app/components/content/examples/table/TableExample.vue" aria-label="View source code"}
This example demonstrates the most common use case of the `Table` component. Check out the source code on GitHub. This example demonstrates the most common use case of the `Table` component. Check out the source code on GitHub.
:: ::
@@ -85,7 +85,7 @@ Use the `columns` prop as an array of [ColumnDef](https://tanstack.com/table/lat
In order to render components or other HTML elements, you will need to use the Vue [`h` function](https://vuejs.org/api/render-function.html#h) inside the `header` and `cell` props. This is different from other components that use slots but allows for more flexibility. In order to render components or other HTML elements, you will need to use the Vue [`h` function](https://vuejs.org/api/render-function.html#h) inside the `header` and `cell` props. This is different from other components that use slots but allows for more flexibility.
::tip{to="#with-slots"} ::tip{to="#with-slots" aria-label="Table columns with slots"}
You can also use slots to customize the header and data cells of the table. You can also use slots to customize the header and data cells of the table.
:: ::

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