Compare commits

...

89 Commits

Author SHA1 Message Date
Romain Hamel
385cbeec6c refactor(Form): remove state assignment and opt-in to nested forms 2025-04-16 18:10:54 +02:00
Romain Hamel
4d875c03a2 chore: up 2025-04-15 12:48:10 +02:00
Romain Hamel
5aea866057 fix(Form): handle schema output types 2025-04-14 19:51:57 +02:00
Benjamin Canac
d5bcb0da59 docs(content): improve examples 2025-04-14 12:42:07 +02:00
Benjamin Canac
0a3cc5a25d docs(SupportedLanguages): add mapping for kk 2025-04-14 11:20:37 +02:00
Benjamin Canac
52735fdd40 docs(nuxt.config): remove hub.vectorize config 2025-04-14 11:18:31 +02:00
renovate[bot]
59fd4d52e1 chore(deps): update all non-major dependencies (v3) (#3854)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-14 11:04:50 +02:00
Iván Máximiliano, Lo Giudice
8e78eb15c8 fix(Form): loses focus on submit (#3796)
Co-authored-by: Romain Hamel <rom.hml@gmail.com>
2025-04-14 11:02:27 +02:00
ElshadBeg
b7fc69baa7 feat(locale): add Uyghur language (#3878) 2025-04-14 10:48:40 +02:00
Altynbek
43153c4e91 feat(locale): add Kazakh language (#3875) 2025-04-14 10:48:14 +02:00
Romain Hamel
d059efca25 feat(unplugin): routing support for inertia (#3845)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2025-04-14 10:47:26 +02:00
Benjamin Canac
eea14155aa fix(types): handle ClassValue in ui prop
Resolves #3860
2025-04-12 18:28:44 +02:00
Benjamin Canac
4ba8503c60 chore(deps): update @nuxt/ui-pro 2025-04-12 18:28:02 +02:00
Maxime Pauvert
fdee2522bb feat(Form): export loading state (#3861) 2025-04-12 17:54:20 +02:00
Benjamin Canac
39c861a64b fix(components): refactor types after @nuxt/module-builder upgrade (#3855) 2025-04-12 17:53:03 +02:00
Jeremy Woertink
333b7e4c9b docs(components): add missing children field in items (#3846) 2025-04-11 14:46:11 +02:00
Nikolay Larsen
f42a79b5ef feat(locale): add Tajik language (#3850) 2025-04-11 14:33:52 +02:00
Hugo Richard
50012d4866 docs(components): update og images (#3859) 2025-04-11 14:26:22 +02:00
Benjamin Canac
b558b8c5aa docs(form): display theme
Resolves #3752
2025-04-10 21:55:36 +02:00
Guillaume Chau
3447a062b6 feat(Tabs): add list-leading and list-trailing slots (#3837)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2025-04-10 21:50:20 +02:00
Sukka
063a23e738 docs(prettier): load from jsdelivr.net instead of unpkg.com (#3575) 2025-04-10 20:53:45 +02:00
renovate[bot]
da0150a9ec chore(deps): update all non-major dependencies (v3) (#3830)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-10 20:45:39 +02:00
Benjamin Canac
981de8b295 docs: prepare for chat components (#3844) 2025-04-10 19:31:12 +02:00
Sandro Circi
fb4c210b41 chore: simplify theme imports in dev (#3851) 2025-04-10 15:59:04 +02:00
Sandro Circi
8c68af5e3b chore: run test suite on windows (#3479)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2025-04-10 11:41:23 +02:00
Hugo Richard
81b46ab880 docs(components): add new og images (#3824) 2025-04-09 18:39:09 +02:00
Benjamin Canac
619b6f2a0e chore(Textarea): put back styles 2025-04-09 17:38:19 +02:00
Benjamin Canac
25913188a7 docs(form): improve joi example 2025-04-09 17:30:04 +02:00
Eugen Istoc
f3098df84a feat(OverlayProvider)!: return an overlay instance from .open() (#3829) 2025-04-09 17:26:12 +02:00
Sandro Circi
3deed4c271 fix(Tree): simplify reusable template types (#3836) 2025-04-09 14:05:59 +02:00
Benjamin Canac
e6b1c238b9 docs(form): improve types 2025-04-09 12:20:38 +02:00
Benjamin Canac
626b023ddb docs(showcase): add learnvue.co 2025-04-09 12:20:38 +02:00
Benjamin Canac
4c667f75f4 chore(package): export utils 2025-04-08 17:48:43 +02:00
Daniel Roe
e04dd53046 chore(deps): bump vue-sfc-transformer (#3822) 2025-04-08 17:33:37 +02:00
Benjamin Canac
d25265c8b7 fix(CommandPalette): consistent alignement with other components 2025-04-08 17:29:02 +02:00
renovate[bot]
7d8353ffdc chore(deps): update all non-major dependencies (v3) (#3806)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-08 15:04:26 +02:00
Benjamin Canac
ba534f18b9 fix(CommandPalette): prevent hover background on disabled items 2025-04-08 15:03:29 +02:00
Benjamin Canac
864083156a feat(Select): handle onSelect field in items 2025-04-08 14:51:53 +02:00
Benjamin Canac
8435a0fe16 fix(InputMenu/SelectMenu): prevent disabled items to be selected
Resolves #3474
2025-04-08 14:43:58 +02:00
Benjamin Canac
d339dcbfb8 fix(Carousel): move arrows inside container on mobile
Resolves #3813
2025-04-08 14:25:26 +02:00
Benjamin Canac
cd7ab413f6 docs(migration): describe onClick change
Resolves #3825
2025-04-08 12:58:31 +02:00
Benjamin Canac
cea881abdc feat(InputMenu/SelectMenu): handle resetSearchTermOnSelect
Resolves #3782
2025-04-08 12:20:28 +02: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
223 changed files with 8765 additions and 5574 deletions

View File

@@ -42,6 +42,8 @@ jobs:
- name: Build application
run: pnpm run docs:build
env:
NODE_OPTIONS: '--max-old-space-size=8192'
- name: Deploy to NuxtHub
uses: nuxt-hub/action@v1

View File

@@ -18,7 +18,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest] # macos-latest, windows-latest
os: [ubuntu-latest, windows-latest] # macos-latest
node: [22]
env:
@@ -65,6 +65,7 @@ jobs:
run: pnpm run dev:vue:build
- name: Publish
if: matrix.os != 'windows-latest'
run: pnpx pkg-pr-new publish --compact --no-template --pnpm
starter-nuxt:

1
.npmrc
View File

@@ -1,4 +1,3 @@
shamefully-hoist=true
auto-install-peers=true
ignore-workspace-root-check=true
shell-emulator=true

View File

@@ -104,6 +104,17 @@ app.mount('#app')
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
- [nuxt/nuxt](https://github.com/nuxt/nuxt)

View File

@@ -7,10 +7,13 @@ export default defineBuildConfig({
'./src/vite'
],
rollup: {
emitCJS: true
},
replace: {
'process.env.DEV': 'false'
replace: {
delimiters: ['', ''],
values: {
// Used in development to import directly from theme
'const isUiDev = true': 'const isUiDev = false'
}
}
},
hooks: {
'mkdist:entry:options'(ctx, entry, options) {

View File

@@ -31,13 +31,10 @@ const component = ({ name, primitive, pro, prose, content }) => {
? `
<script lang="ts">
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/${path}/${prose ? 'prose/' : ''}${content ? 'content/' : ''}${kebabName}'
import { tv } from '${pro ? '#ui/utils/tv' : '../utils/tv'}'
import type { ComponentConfig } from '../types/utils'
const appConfig${camelName} = _appConfig as AppConfig & { ${key}: { ${prose ? 'prose: { ' : ''}${camelName}: Partial<typeof theme> } }${prose ? ' }' : ''}
const ${camelName} = tv({ extend: tv(theme), ...(appConfig${camelName}.${key}?.${prose ? 'prose?.' : ''}${camelName} || {}) })
type ${upperName} = ComponentConfig<typeof theme, AppConfig, ${upperName}${pro ? `, '${key}'` : ''}>
export interface ${upperName}Props {
/**
@@ -46,7 +43,7 @@ export interface ${upperName}Props {
*/
as?: any
class?: any
ui?: Partial<typeof ${camelName}.slots>
ui?: ${upperName}['slots']
}
export interface ${upperName}Slots {
@@ -55,12 +52,17 @@ export interface ${upperName}Slots {
</script>
<script setup lang="ts">
import { computed } from 'vue'
import { Primitive } from 'reka-ui'
import { useAppConfig } from '#imports'
import { tv } from '../utils/tv'
const props = defineProps<${upperName}Props>()
defineSlots<${upperName}Slots>()
const ui = ${camelName}()
const appConfig = useAppConfig() as ${upperName}['AppConfig']
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.${camelName} || {}) })())
</script>
<template>
@@ -71,22 +73,16 @@ const ui = ${camelName}()
`
: `
<script lang="ts">
import type { VariantProps } from 'tailwind-variants'
import type { ${upperName}RootProps, ${upperName}RootEmits } from 'reka-ui'
import type { AppConfig } from '@nuxt/schema'
import _appConfig from '#build/app.config'
import theme from '#build/${path}/${prose ? 'prose/' : ''}${content ? 'content/' : ''}${kebabName}'
import { tv } from '${pro ? '#ui/utils/tv' : '../utils/tv'}'
import type { ComponentConfig } from '../types/utils'
const appConfig${camelName} = _appConfig as AppConfig & { ${key}: { ${prose ? 'prose: { ' : ''}${camelName}: Partial<typeof theme> } }${prose ? ' }' : ''}
const ${camelName} = tv({ extend: tv(theme), ...(appConfig${camelName}.${key}?.${prose ? 'prose?.' : ''}${camelName} || {}) })
type ${upperName}Variants = VariantProps<typeof ${camelName}>
type ${upperName} = ComponentConfig<typeof theme, AppConfig, ${upperName}${pro ? `, '${key}'` : ''}>
export interface ${upperName}Props extends Pick<${upperName}RootProps> {
class?: any
ui?: Partial<typeof ${camelName}.slots>
ui?: ${upperName}['slots']
}
export interface ${upperName}Emits extends ${upperName}RootEmits {}
@@ -95,16 +91,21 @@ export interface ${upperName}Slots {}
</script>
<script setup lang="ts">
import { computed } from 'vue'
import { ${upperName}Root, useForwardPropsEmits } from 'reka-ui'
import { reactivePick } from '@vueuse/core'
import { useAppConfig } from '#imports'
import { tv } from '../utils/tv'
const props = defineProps<${upperName}Props>()
const emits = defineEmits<${upperName}Emits>()
const slots = defineSlots<${upperName}Slots>()
const appConfig = useAppConfig() as ${upperName}['AppConfig']
const rootProps = useForwardPropsEmits(reactivePick(props), emits)
const ui = ${camelName}()
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.${camelName} || {}) })())
</script>
<template>

View File

@@ -12,6 +12,7 @@ const { data: files } = useLazyAsyncData('search', () => queryCollectionSearchSe
})
const links = useLinks()
const searchLinks = useSearchLinks()
const color = computed(() => colorMode.value === 'dark' ? (colors as any)[appConfig.ui.colors.neutral][900] : 'white')
const radius = computed(() => `:root { --ui-radius: ${appConfig.theme.radius}rem; }`)
const blackAsPrimary = computed(() => appConfig.theme.blackAsPrimary ? `:root { --ui-primary: black; } .dark { --ui-primary: white; }` : ':root {}')
@@ -64,6 +65,7 @@ provide('navigation', mappedNavigation)
<ClientOnly>
<LazyUContentSearch
:links="searchLinks"
:files="files"
:groups="[{
id: 'framework',
@@ -83,5 +85,5 @@ provide('navigation', mappedNavigation)
</template>
<style>
/* Safelist (do not remove): [&>div]:*:my-0 [&>div]:*:w-full h-64 !px-0 !py-0 !pt-0 !pb-0 !p-0 !justify-start !justify-end !min-h-96 h-136 */
/* Safelist (do not remove): [&>div]:*:my-0 [&>div]:*:w-full h-64 !px-0 !py-0 !pt-0 !pb-0 !p-0 !justify-start !justify-end !min-h-96 h-136 max-h-[341px] */
</style>

View File

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

View File

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

View File

@@ -14,6 +14,7 @@ const props = withDefaults(defineProps<{
color?: string
size?: { min: number, max: number }
speed?: 'slow' | 'normal' | 'fast'
isIndex?: boolean
}>(), {
starCount: 50,
color: 'var(--ui-primary)',
@@ -21,7 +22,8 @@ const props = withDefaults(defineProps<{
min: 1,
max: 3
}),
speed: 'normal'
speed: 'normal',
isIndex: false
})
const route = useRoute()
@@ -53,7 +55,7 @@ const twinkleDuration = computed(() => {
</script>
<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
v-for="star in stars"
:key="star.id"

View File

@@ -328,7 +328,7 @@ const { data: ast } = await useAsyncData(`component-code-${name}-${hash({ props:
<template>
<div class="my-5">
<div>
<div class="relative">
<div v-if="options.length" class="flex flex-wrap items-center gap-2.5 border border-(--ui-border-muted) border-b-0 relative rounded-t-[calc(var(--ui-radius)*1.5)] px-4 py-2.5 overflow-x-auto">
<template v-for="option in options" :key="option.name">
<UFormField

View File

@@ -22,6 +22,7 @@ function getEmojiFlag(locale: string): string {
hi: 'in', // Hindi -> India
hy: 'am', // Armenian -> Armenia
ja: 'jp', // Japanese -> Japan
kk: 'kz', // Kazakh -> Kazakhstan
km: 'kh', // Khmer -> Cambodia
ko: 'kr', // Korean -> South Korea
nb: 'no', // Norwegian Bokmål -> Norway

View File

@@ -11,7 +11,6 @@ const items = shallowRef<AccordionItem[]>([
{
label: 'Colors',
icon: 'i-lucide-swatch-book',
slot: 'colors' as const,
content: 'Choose a primary and a neutral color from your Tailwind CSS theme.'
},
{

View File

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

View File

@@ -1,16 +1,20 @@
<script setup lang="ts">
import type { BreadcrumbItem } from '@nuxt/ui'
const items: BreadcrumbItem[] = [{
label: 'Home',
to: '/'
}, {
label: 'Components',
to: '/components'
}, {
label: 'Breadcrumb',
to: '/components/breadcrumb'
}]
const items: BreadcrumbItem[] = [
{
label: 'Home',
to: '/'
},
{
label: 'Components',
to: '/components'
},
{
label: 'Breadcrumb',
to: '/components/breadcrumb'
}
]
</script>
<template>

View File

@@ -1,23 +1,30 @@
<script setup lang="ts">
import type { DropdownMenuItem } from '@nuxt/ui'
const items: DropdownMenuItem[] = [{
label: 'Team',
icon: 'i-lucide-users'
}, {
label: 'Invite users',
icon: 'i-lucide-user-plus',
children: [{
label: 'Invite by email',
icon: 'i-lucide-send-horizontal'
}, {
label: 'Invite by link',
icon: 'i-lucide-link'
}]
}, {
label: 'New team',
icon: 'i-lucide-plus'
}]
const items: DropdownMenuItem[] = [
{
label: 'Team',
icon: 'i-lucide-users'
},
{
label: 'Invite users',
icon: 'i-lucide-user-plus',
children: [
{
label: 'Invite by email',
icon: 'i-lucide-send-horizontal'
},
{
label: 'Invite by link',
icon: 'i-lucide-link'
}
]
},
{
label: 'New team',
icon: 'i-lucide-plus'
}
]
</script>
<template>

View File

@@ -1,76 +1,79 @@
<script setup lang="ts">
const groups = [{
id: 'settings',
items: [
{
label: 'Profile',
icon: 'i-lucide-user',
kbds: ['meta', 'P']
},
{
label: 'Billing',
icon: 'i-lucide-credit-card',
kbds: ['meta', 'B'],
slot: 'billing' as const
},
{
label: 'Notifications',
icon: 'i-lucide-bell'
},
{
label: 'Security',
icon: 'i-lucide-lock'
}
]
}, {
id: 'users',
label: 'Users',
slot: 'users' as const,
items: [
{
label: 'Benjamin Canac',
suffix: 'benjamincanac',
to: 'https://github.com/benjamincanac',
target: '_blank'
},
{
label: 'Sylvain Marroufin',
suffix: 'smarroufin',
to: 'https://github.com/smarroufin',
target: '_blank'
},
{
label: 'Sébastien Chopin',
suffix: 'atinux',
to: 'https://github.com/atinux',
target: '_blank'
},
{
label: 'Romain Hamel',
suffix: 'romhml',
to: 'https://github.com/romhml',
target: '_blank'
},
{
label: 'Haytham A. Salama',
suffix: 'Haythamasalama',
to: 'https://github.com/Haythamasalama',
target: '_blank'
},
{
label: 'Daniel Roe',
suffix: 'danielroe',
to: 'https://github.com/danielroe',
target: '_blank'
},
{
label: 'Neil Richter',
suffix: 'noook',
to: 'https://github.com/noook',
target: '_blank'
}
]
}]
const groups = [
{
id: 'settings',
items: [
{
label: 'Profile',
icon: 'i-lucide-user',
kbds: ['meta', 'P']
},
{
label: 'Billing',
icon: 'i-lucide-credit-card',
kbds: ['meta', 'B'],
slot: 'billing' as const
},
{
label: 'Notifications',
icon: 'i-lucide-bell'
},
{
label: 'Security',
icon: 'i-lucide-lock'
}
]
},
{
id: 'users',
label: 'Users',
slot: 'users' as const,
items: [
{
label: 'Benjamin Canac',
suffix: 'benjamincanac',
to: 'https://github.com/benjamincanac',
target: '_blank'
},
{
label: 'Sylvain Marroufin',
suffix: 'smarroufin',
to: 'https://github.com/smarroufin',
target: '_blank'
},
{
label: 'Sébastien Chopin',
suffix: 'atinux',
to: 'https://github.com/atinux',
target: '_blank'
},
{
label: 'Romain Hamel',
suffix: 'romhml',
to: 'https://github.com/romhml',
target: '_blank'
},
{
label: 'Haytham A. Salama',
suffix: 'Haythamasalama',
to: 'https://github.com/Haythamasalama',
target: '_blank'
},
{
label: 'Daniel Roe',
suffix: 'danielroe',
to: 'https://github.com/danielroe',
target: '_blank'
},
{
label: 'Neil Richter',
suffix: 'noook',
to: 'https://github.com/noook',
target: '_blank'
}
]
}
]
</script>
<template>

View File

@@ -3,14 +3,18 @@ import type { ContextMenuItem } from '@nuxt/ui'
const loading = ref(true)
const items: ContextMenuItem[] = [{
label: 'Refresh the Page',
slot: 'refresh'
}, {
label: 'Clear Cookies and Refresh'
}, {
label: 'Clear Cache and Refresh'
}]
const items = [
{
label: 'Refresh the Page',
slot: 'refresh' as const
},
{
label: 'Clear Cookies and Refresh'
},
{
label: 'Clear Cache and Refresh'
}
] satisfies ContextMenuItem[]
</script>
<template>

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

@@ -14,7 +14,7 @@ const validate = (state: any): FormError[] => {
}
const toast = useToast()
async function onSubmit(event: FormSubmitEvent<any>) {
async function onSubmit(event: FormSubmitEvent<typeof state>) {
toast.add({ title: 'Success', description: 'The form has been submitted.', color: 'success' })
console.log(event.data)
}

View File

@@ -34,9 +34,10 @@ const schema = z.object({
pin: z.string().regex(/^\d$/).array().length(5)
})
type Schema = z.input<typeof schema>
type Input = z.input<typeof schema>
type Output = z.output<typeof schema>
const state = reactive<Partial<Schema>>({})
const state = reactive<Partial<Input>>({})
const form = useTemplateRef('form')
@@ -47,7 +48,7 @@ const items = [
]
const toast = useToast()
async function onSubmit(event: FormSubmitEvent<any>) {
async function onSubmit(event: FormSubmitEvent<Output>) {
toast.add({ title: 'Success', description: 'The form has been submitted.', color: 'success' })
console.log(event.data)
}

View File

@@ -15,7 +15,7 @@ const state = reactive({
})
const toast = useToast()
async function onSubmit(event: FormSubmitEvent<any>) {
async function onSubmit(event: FormSubmitEvent<typeof state>) {
toast.add({ title: 'Success', description: 'The form has been submitted.', color: 'success' })
console.log(event.data)
}

View File

@@ -18,7 +18,7 @@ type NestedSchema = z.output<typeof nestedSchema>
const state = reactive<Partial<Schema & NestedSchema>>({ })
const toast = useToast()
async function onSubmit(event: FormSubmitEvent<any>) {
async function onSubmit(event: FormSubmitEvent<Schema>) {
toast.add({ title: 'Success', description: 'The form has been submitted.', color: 'success' })
console.log(event.data)
}
@@ -39,7 +39,7 @@ async function onSubmit(event: FormSubmitEvent<any>) {
<UCheckbox v-model="state.news" name="news" label="Register to our newsletter" @update:model-value="state.email = undefined" />
</div>
<UForm v-if="state.news" :state="state" :schema="nestedSchema">
<UForm v-if="state.news" :state="state" :schema="nestedSchema" nested>
<UFormField label="Email" name="email">
<UInput v-model="state.email" placeholder="john@lennon.com" />
</UFormField>

View File

@@ -34,7 +34,7 @@ function removeItem() {
const toast = useToast()
async function onSubmit(event: FormSubmitEvent<any>) {
async function onSubmit(event: FormSubmitEvent<Schema>) {
toast.add({ title: 'Success', description: 'The form has been submitted.', color: 'success' })
console.log(event.data)
}
@@ -51,7 +51,14 @@ async function onSubmit(event: FormSubmitEvent<any>) {
<UInput v-model="state.customer" placeholder="Wonka Industries" />
</UFormField>
<UForm v-for="item, count in state.items" :key="count" :state="item" :schema="itemSchema" class="flex gap-2">
<UForm
v-for="item, count in state.items"
:key="count"
:state="item"
:schema="itemSchema"
nested
class="flex gap-2"
>
<UFormField :label="!count ? 'Description' : undefined" name="description">
<UInput v-model="item.description" />
</UFormField>

View File

@@ -14,7 +14,7 @@ const validate = (state: any): FormError[] => {
}
const toast = useToast()
async function onSubmit(event: FormSubmitEvent<any>) {
async function onSubmit(event: FormSubmitEvent<typeof state>) {
toast.add({ title: 'Success', description: 'The form has been submitted.', color: 'success' })
console.log(event.data)
}

View File

@@ -1,23 +1,21 @@
<script setup lang="ts">
import type { NavigationMenuItem } from '@nuxt/ui'
const items: NavigationMenuItem[] = [
const items = [
{
label: 'Guide',
icon: 'i-lucide-book-open'
},
{
label: 'Composables',
icon: 'i-lucide-database'
},
{
label: 'Components',
icon: 'i-lucide-box',
slot: 'components'
slot: 'components' as const
}
]
] satisfies NavigationMenuItem[]
</script>
<template>

View File

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

View File

@@ -1,23 +1,23 @@
<script setup lang="ts">
import type { StepperItem } from '@nuxt/ui'
const items: StepperItem[] = [
const items = [
{
slot: 'address',
slot: 'address' as const,
title: 'Address',
description: 'Add your address here',
icon: 'i-lucide-house'
}, {
slot: 'shipping',
slot: 'shipping' as const,
title: 'Shipping',
description: 'Set your preferred shipping method',
icon: 'i-lucide-truck'
}, {
slot: 'checkout',
slot: 'checkout' as const,
title: 'Checkout',
description: 'Confirm your order'
}
]
] satisfies StepperItem[]
</script>
<template>

View File

@@ -3,17 +3,14 @@ import type { StepperItem } from '@nuxt/ui'
const items: StepperItem[] = [
{
slot: 'address',
title: 'Address',
description: 'Add your address here',
icon: 'i-lucide-house'
}, {
slot: 'shipping',
title: 'Shipping',
description: 'Set your preferred shipping method',
icon: 'i-lucide-truck'
}, {
slot: 'checkout',
title: 'Checkout',
description: 'Confirm your order'
}

View File

@@ -6,21 +6,23 @@ const items = [
label: 'app/',
slot: 'app' as const,
defaultExpanded: true,
children: [{
label: 'composables/',
children: [
{ label: 'useAuth.ts', icon: 'i-vscode-icons-file-type-typescript' },
{ label: 'useUser.ts', icon: 'i-vscode-icons-file-type-typescript' }
]
},
{
label: 'components/',
defaultExpanded: true,
children: [
{ label: 'Card.vue', icon: 'i-vscode-icons-file-type-vue' },
{ label: 'Button.vue', icon: 'i-vscode-icons-file-type-vue' }
]
}]
children: [
{
label: 'composables/',
children: [
{ label: 'useAuth.ts', icon: 'i-vscode-icons-file-type-typescript' },
{ label: 'useUser.ts', icon: 'i-vscode-icons-file-type-typescript' }
]
},
{
label: 'components/',
defaultExpanded: true,
children: [
{ label: 'Card.vue', icon: 'i-vscode-icons-file-type-vue' },
{ label: 'Button.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' }

View File

@@ -5,22 +5,24 @@ const items: TreeItem[] = [
{
label: 'app/',
value: 'app',
children: [{
label: 'composables/',
value: 'composables',
children: [
{ label: 'useAuth.ts', icon: 'i-vscode-icons-file-type-typescript' },
{ label: 'useUser.ts', icon: 'i-vscode-icons-file-type-typescript' }
]
},
{
label: 'components/',
value: 'components',
children: [
{ label: 'Card.vue', icon: 'i-vscode-icons-file-type-vue' },
{ label: 'Button.vue', icon: 'i-vscode-icons-file-type-vue' }
]
}]
children: [
{
label: 'composables/',
value: 'composables',
children: [
{ label: 'useAuth.ts', icon: 'i-vscode-icons-file-type-typescript' },
{ label: 'useUser.ts', icon: 'i-vscode-icons-file-type-typescript' }
]
},
{
label: 'components/',
value: 'components',
children: [
{ label: 'Card.vue', icon: 'i-vscode-icons-file-type-vue' },
{ label: 'Button.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' }

View File

@@ -5,21 +5,23 @@ const items: TreeItem[] = [
{
label: 'app/',
defaultExpanded: true,
children: [{
label: 'composables/',
children: [
{ label: 'useAuth.ts', icon: 'i-vscode-icons-file-type-typescript' },
{ label: 'useUser.ts', icon: 'i-vscode-icons-file-type-typescript' }
]
},
{
label: 'components/',
defaultExpanded: true,
children: [
{ label: 'Card.vue', icon: 'i-vscode-icons-file-type-vue' },
{ label: 'Button.vue', icon: 'i-vscode-icons-file-type-vue' }
]
}]
children: [
{
label: 'composables/',
children: [
{ label: 'useAuth.ts', icon: 'i-vscode-icons-file-type-typescript' },
{ label: 'useUser.ts', icon: 'i-vscode-icons-file-type-typescript' }
]
},
{
label: 'components/',
defaultExpanded: true,
children: [
{ label: 'Card.vue', icon: 'i-vscode-icons-file-type-vue' },
{ label: 'Button.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' }

View File

@@ -8,21 +8,23 @@ const items: TreeItem[] = [
onSelect: (e: Event) => {
e.preventDefault()
},
children: [{
label: 'composables/',
children: [
{ label: 'useAuth.ts', icon: 'i-vscode-icons-file-type-typescript' },
{ label: 'useUser.ts', icon: 'i-vscode-icons-file-type-typescript' }
]
},
{
label: 'components/',
defaultExpanded: true,
children: [
{ label: 'Card.vue', icon: 'i-vscode-icons-file-type-vue' },
{ label: 'Button.vue', icon: 'i-vscode-icons-file-type-vue' }
]
}]
children: [
{
label: 'composables/',
children: [
{ label: 'useAuth.ts', icon: 'i-vscode-icons-file-type-typescript' },
{ label: 'useUser.ts', icon: 'i-vscode-icons-file-type-typescript' }
]
},
{
label: 'components/',
defaultExpanded: true,
children: [
{ label: 'Card.vue', icon: 'i-vscode-icons-file-type-vue' },
{ label: 'Button.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' }

View File

@@ -8,21 +8,23 @@ const items: TreeItem[] = [
onToggle: (e: Event) => {
e.preventDefault()
},
children: [{
label: 'composables/',
children: [
{ label: 'useAuth.ts', icon: 'i-vscode-icons-file-type-typescript' },
{ label: 'useUser.ts', icon: 'i-vscode-icons-file-type-typescript' }
]
},
{
label: 'components/',
defaultExpanded: true,
children: [
{ label: 'Card.vue', icon: 'i-vscode-icons-file-type-vue' },
{ label: 'Button.vue', icon: 'i-vscode-icons-file-type-vue' }
]
}]
children: [
{
label: 'composables/',
children: [
{ label: 'useAuth.ts', icon: 'i-vscode-icons-file-type-typescript' },
{ label: 'useUser.ts', icon: 'i-vscode-icons-file-type-typescript' }
]
},
{
label: 'components/',
defaultExpanded: true,
children: [
{ label: 'Card.vue', icon: 'i-vscode-icons-file-type-vue' },
{ label: 'Button.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' }

View File

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

View File

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

View File

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

View File

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

View File

@@ -67,9 +67,9 @@ if (!import.meta.prerender) {
const type = page.value?.path.includes('components') ? 'Vue Component ' : page.value?.path.includes('composables') ? 'Vue Composable ' : ''
useSeoMeta({
titleTemplate: `%s ${type}- Nuxt UI ${page.value.module === 'ui-pro' ? 'Pro' : ''} 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,
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,
ogDescription: page.value.description
})
@@ -112,7 +112,7 @@ const communityLinks = computed(() => [{
to: 'https://nuxt.lemonsqueezy.com/affiliates',
target: '_blank'
}, {
icon: 'i-lucide-life-buoy',
icon: 'i-lucide-git-pull-request-arrow',
label: 'Contribution',
to: '/getting-started/contribution'
}, {

View File

@@ -7,7 +7,7 @@ const title = 'Vue Components'
const description = 'Explore 99+ customizable UI components for Vue and Nuxt built with Tailwind CSS and Reka UI.'
useSeoMeta({
titleTemplate: `%s - Nuxt UI`,
titleTemplate: '%s - Nuxt UI',
title,
description,
ogTitle: `${title} - Nuxt UI`,

View File

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

View File

@@ -23,18 +23,7 @@ const { data: components } = await useAsyncData('ui-components', () => {
.all()
})
const { data: module } = await useFetch<{
stats: {
downloads: number
stars: number
}
contributors: {
username: string
}[]
}>('https://api.nuxt.com/modules/ui', {
key: 'stats',
transform: ({ stats, contributors }) => ({ stats, contributors })
})
const { data: module } = await useFetch('/api/module.json')
const { format } = Intl.NumberFormat('en', { notation: 'compact' })
@@ -85,7 +74,7 @@ useIntersectionObserver(contributorsRef, ([entry]) => {
</div>
</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">
<UPageMarquee
@@ -176,12 +165,12 @@ useIntersectionObserver(contributorsRef, ([entry]) => {
<circle cx="6.53711" cy="37.4551" r="1.5" fill="var(--ui-border-accented)" />
<circle cx="38.5957" cy="37.4551" r="1.5" fill="var(--ui-border-accented)" />
</svg>
<UIcon :name="feature.icon" class="size-5 flex-shrink-0" />
<UIcon :name="feature.icon" class="size-5 shrink-0" />
</div>
<div class="flex flex-col">
<h2 class="font-medium text-(--ui-text-highlighted) inline-flex items-center gap-x-1">
{{ 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>
<p class="text-sm text-(--ui-text-muted)">
{{ feature.description }}
@@ -313,8 +302,8 @@ useIntersectionObserver(contributorsRef, ([entry]) => {
:src="`/pro/blocks/image${i}.png`"
width="460"
height="258"
:alt="`Nuxt UI Pro Screenshot ${i}`"
loading="lazy"
:alt="`Nuxt UI Pro Screenshot ${i}`"
class="aspect-video border border-(--ui-border) rounded-[calc(var(--ui-radius)*2)] bg-white"
>
</UPageMarquee>

View File

@@ -12,7 +12,7 @@ const { url } = useSiteConfig()
useSeoMeta({
title,
description,
ogTitle: `${title} - Nuxt UI Pro`,
ogTitle: title,
ogDescription: description,
ogImage: joinURL(url, '/pro/og-image.png')
})
@@ -33,7 +33,7 @@ const activating = ref(false)
const successMessage = ref()
const errorMessage = ref('')
async function submit(event: FormSubmitEvent<any>) {
async function submit(event: FormSubmitEvent<Schema>) {
activating.value = true
errorMessage.value = ''
successMessage.value = ''

View File

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

View File

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

View File

@@ -67,7 +67,7 @@ useSeoMeta({
:items="(template.images as any[])"
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>
<Placeholder v-else class="w-full h-full aspect-video" />
</Motion>

View File

@@ -13,7 +13,7 @@ const description = page.value.description
useSeoMeta({
title,
description,
ogTitle: `${title} - Nuxt UI Pro`,
ogTitle: title,
ogDescription: description,
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({
titleTemplate: '%s - Nuxt UI',
title,
ogTitle: 'Nuxt UI Roadmap',
description
description,
ogTitle: `${title} - Nuxt UI`,
ogDescription: description
})
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

@@ -16,12 +16,12 @@ function handleMessage(message) {
async function handleFormatMessage(message) {
if (!globalThis.prettier) {
await Promise.all([
import('https://unpkg.com/prettier@3.5.2/standalone.js'),
import('https://unpkg.com/prettier@3.5.2/plugins/babel.js'),
import('https://unpkg.com/prettier@3.5.2/plugins/estree.js'),
import('https://unpkg.com/prettier@3.5.2/plugins/html.js'),
import('https://unpkg.com/prettier@3.5.2/plugins/markdown.js'),
import('https://unpkg.com/prettier@3.5.2/plugins/typescript.js')
import('https://cdn.jsdelivr.net/npm/prettier@3.5.2/standalone.js'),
import('https://cdn.jsdelivr.net/npm/prettier@3.5.2/plugins/babel.js'),
import('https://cdn.jsdelivr.net/npm/prettier@3.5.2/plugins/estree.js'),
import('https://cdn.jsdelivr.net/npm/prettier@3.5.2/plugins/html.js'),
import('https://cdn.jsdelivr.net/npm/prettier@3.5.2/plugins/markdown.js'),
import('https://cdn.jsdelivr.net/npm/prettier@3.5.2/plugins/typescript.js')
])
}

View File

@@ -1,6 +1,18 @@
import { defineCollection, z } from '@nuxt/content'
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({
category: z.enum(['layout', 'form', 'element', 'navigation', 'data', 'overlay']).optional(),
framework: z.string().optional(),
@@ -42,5 +54,26 @@ export const collections = {
include: '**/*'
}, pro!].filter(Boolean),
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

@@ -180,7 +180,7 @@ In Nuxt UI v2, we had a mix between a design system with `primary`, `gray`, `err
This change introduces several breaking changes that you need to be aware of:
1. The `gray` color has been renamed to `neutral`
- The `gray` color has been renamed to `neutral`
```diff
<template>
@@ -203,7 +203,7 @@ You can also use the new [design tokens](/getting-started/theme#neutral-palette)
```
::
2. The `DEFAULT` shade that let you write `text-primary` no longer exists, you can use [color shades](/getting-started/theme#color-shades) instead:
- The `DEFAULT` shade that let you write `text-primary` no longer exists, you can use [color shades](/getting-started/theme#color-shades) instead:
```diff
<template>
@@ -212,7 +212,7 @@ You can also use the new [design tokens](/getting-started/theme#neutral-palette)
</template>
```
3. The `gray`, `black` and `white` in the `color` props have been removed in favor of `neutral`:
- The `gray`, `black` and `white` in the `color` props have been removed in favor of `neutral`:
```diff
- <UButton color="black" />
@@ -225,7 +225,7 @@ You can also use the new [design tokens](/getting-started/theme#neutral-palette)
+ <UButton color="neutral" variant="outline" />
```
4. You can no longer use Tailwind CSS colors in the `color` props, use the new aliases instead:
- You can no longer use Tailwind CSS colors in the `color` props, use the new aliases instead:
```diff
- <UButton color="red" />
@@ -236,7 +236,7 @@ You can also use the new [design tokens](/getting-started/theme#neutral-palette)
Learn how to extend the design system to add new color aliases.
::
5. The color configuration in `app.config.ts` has been moved into a `colors` object:
- The color configuration in `app.config.ts` has been moved into a `colors` object:
```diff
export default defineAppConfig({
@@ -255,7 +255,7 @@ export default defineAppConfig({
Nuxt UI components are now styled using the [Tailwind Variants API](/getting-started/theme#components-theme), which makes all the overrides you made using the `app.config.ts` and the `ui` prop obsolete.
1. Update your [`app.config.ts`](/getting-started/theme#config) to override components with their new theme:
- Update your [`app.config.ts`](/getting-started/theme#config) to override components with their new theme:
```diff
export default defineAppConfig({
@@ -278,7 +278,7 @@ export default defineAppConfig({
})
```
2. Update your [`ui` props](/getting-started/theme#props) to override each component's slots using their new theme:
- Update your [`ui` props](/getting-started/theme#props) to override each component's slots using their new theme:
```diff
<template>
@@ -351,7 +351,7 @@ Here are the Nuxt UI Pro components that have been renamed or removed:
In addition to the renamed components, there are lots of changes to the components API. Let's detail the most important ones:
1. The `links` and `options` props have been renamed to `items` for consistency:
- The `links` and `options` props have been renamed to `items` for consistency:
```diff
<template>
@@ -367,7 +367,25 @@ In addition to the renamed components, there are lots of changes to the componen
This change affects the following components: `Breadcrumb`, `HorizontalNavigation`, `InputMenu`, `RadioGroup`, `Select`, `SelectMenu`, `VerticalNavigation`.
::
2. The global `Modals`, `Slideovers` and `Notifications` components have been removed in favor the [App](/components/app) component:
- The `click` field in different components has been removed in favor of the native Vue `onClick` event:
```diff
<script setup lang="ts">
const items = [{
label: 'Edit',
- click: () => {
+ onClick: () => {
console.log('Edit')
}
}]
</script>
```
::note
This change affects the `Toast` component as well as all component that have `items` links like `NavigationMenu`, `DropdownMenu`, `CommandPalette`, etc.
::
- The global `Modals`, `Slideovers` and `Notifications` components have been removed in favor the [App](/components/app) component:
```diff [app.vue]
<template>
@@ -380,7 +398,7 @@ This change affects the following components: `Breadcrumb`, `HorizontalNavigatio
</template>
```
3. The `v-model:open` directive and `default-open` prop are now used to control visibility:
- The `v-model:open` directive and `default-open` prop are now used to control visibility:
```diff
<template>
@@ -393,7 +411,7 @@ This change affects the following components: `Breadcrumb`, `HorizontalNavigatio
This change affects the following components: `ContextMenu`, `Modal` and `Slideover` and enables controlling visibility for `InputMenu`, `Select`, `SelectMenu` and `Tooltip`.
::
4. The default slot is now used for the trigger and the content goes inside the `#content` slot (you don't need to use a `v-model:open` directive with this method):
- The default slot is now used for the trigger and the content goes inside the `#content` slot (you don't need to use a `v-model:open` directive with this method):
```diff
<script setup lang="ts">
@@ -420,7 +438,7 @@ This change affects the following components: `ContextMenu`, `Modal` and `Slideo
This change affects the following components: `Modal`, `Popover`, `Slideover`, `Tooltip`.
::
5. A `#header`, `#body` and `#footer` slots have been added inside the `#content` slot like the `Card` component:
- A `#header`, `#body` and `#footer` slots have been added inside the `#content` slot like the `Card` component:
```diff
<template>
@@ -439,10 +457,9 @@ This change affects the following components: `Modal`, `Popover`, `Slideover`, `
This change affects the following components: `Modal`, `Slideover`.
::
### Changed composables
1. The `useToast()` composable `timeout` prop has been renamed to `duration`:
- The `useToast()` composable `timeout` prop has been renamed to `duration`:
```diff
<script setup lang="ts">
@@ -453,7 +470,7 @@ const toast = useToast()
</script>
```
2. The `useModal` and `useSlideover` composables have been removed in favor of a more generic `useOverlay` composable:
- The `useModal` and `useSlideover` composables have been removed in favor of a more generic `useOverlay` composable:
Some important differences:
- The `useOverlay` composable is now used to create overlay instances

View File

@@ -32,6 +32,7 @@ Use the `items` prop as an array of objects with the following properties:
- [`slot?: string`{lang="ts-type"}](#with-custom-slot)
- `onSelect?(e: Event): void`{lang="ts-type"}
- [`onUpdateChecked?(checked: boolean): void`{lang="ts-type"}](#with-checkbox-items)
- `children?: ContextMenuItem[] | ContextMenuItem[][]`{lang="ts-type"}
You can pass any property from the [Link](/components/link#props) component such as `to`, `target`, etc.

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.
::
### 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
Use the `#footer` slot to add content after the Drawer's body.

View File

@@ -32,6 +32,7 @@ Use the `items` prop as an array of objects with the following properties:
- [`slot?: string`{lang="ts-type"}](#with-custom-slot)
- `onSelect?(e: Event): void`{lang="ts-type"}
- [`onUpdateChecked?(checked: boolean): void`{lang="ts-type"}](#with-checkbox-items)
- `children?: DropdownMenuItem[] | DropdownMenuItem[][]`{lang="ts-type"}
You can pass any property from the [Link](/components/link#props) component such as `to`, `target`, etc.

View File

@@ -32,7 +32,7 @@ It requires two props:
class: 'w-60'
---
::
::component-example{label="Zod"}
---
name: 'form-example-zod'
@@ -206,3 +206,7 @@ This will give you access to the following:
| `dirtyFields`{lang="ts-type"} | `DeepReadonly<Set<keyof T>>`{lang="ts-type"} Tracks fields that have been modified by the user. |
| `touchedFields`{lang="ts-type"} | `DeepReadonly<Set<keyof T>>`{lang="ts-type"} Tracks fields that the user interacted with. |
| `blurredFields`{lang="ts-type"} | `DeepReadonly<Set<keyof T>>`{lang="ts-type"} Tracks fields blurred by the user. |
## Theme
:component-theme

View File

@@ -28,6 +28,7 @@ Use the `items` prop as an array of objects with the following properties:
- `class?: any`{lang="ts-type"}
- [`slot?: string`{lang="ts-type"}](#with-custom-slot)
- `onSelect?(e: Event): void`{lang="ts-type"}
- `children?: NavigationMenuChildItem[]`{lang="ts-type"}
You can pass any property from the [Link](/components/link#props) component such as `to`, `target`, etc.

View File

@@ -177,6 +177,12 @@ props:
: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
:component-theme

View File

@@ -17,7 +17,7 @@ Use the `v-model` directive to control the value of the RadioGroup or the `defau
### 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
---
@@ -133,30 +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
externalTypes:
- RadioGroupItem[]
props:
orientation: 'horizontal'
defaultValue: 'System'
items:
- 'System'
- 'Light'
- 'Dark'
---
::
### Color
Use the `color` prop to change the color of the RadioGroup.
@@ -181,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
Use the `size` prop to change the size of the RadioGroup.
@@ -197,6 +202,57 @@ externalTypes:
- RadioGroupItem[]
props:
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'
items:
- 'System'

View File

@@ -22,6 +22,17 @@ props:
---
::
### Rows
Use the `rows` prop to set the number of rows. Defaults to `3`.
::component-code
---
props:
rows: 12
---
::
### Placeholder
Use the `placeholder` prop to set a placeholder text.
@@ -33,6 +44,37 @@ props:
---
::
### Autoresize
Use the `autoresize` prop to enable autoresizing the height of the Textarea.
::component-code
---
ignore:
- modelValue
external:
- modelValue
props:
modelValue: 'This is a long text that will autoresize the height of the Textarea.'
autoresize: true
---
::
Use the `maxrows` prop to set the maximum number of rows when autoresizing. If set to `0`, the Textarea will grow indefinitely.
::component-code
---
ignore:
- modelValue
external:
- modelValue
props:
modelValue: 'This is a long text that will autoresize the height of the Textarea with a maximum of 4 rows.'
maxrows: 4
autoresize: true
---
::
### Color
Use the `color` prop to change the ring color when the Textarea is focused.
@@ -82,6 +124,102 @@ props:
---
::
### Icon :badge{label="Not released" class="align-text-top"}
Use the `icon` prop to show an [Icon](/components/icon) inside the Textarea.
::component-code
---
prettier: true
ignore:
- placeholder
props:
icon: 'i-lucide-search'
size: md
variant: outline
placeholder: 'Search...'
rows: 1
---
::
Use the `leading` and `trailing` props to set the icon position or the `leading-icon` and `trailing-icon` props to set a different icon for each position.
::component-code
---
prettier: true
ignore:
- placeholder
props:
trailingIcon: i-lucide-at-sign
placeholder: 'Enter your email'
size: md
rows: 1
---
::
### Avatar :badge{label="Not released" class="align-text-top"}
Use the `avatar` prop to show an [Avatar](/components/avatar) inside the Textarea.
::component-code
---
prettier: true
ignore:
- placeholder
props:
avatar:
src: 'https://github.com/nuxt.png'
size: md
variant: outline
placeholder: 'Search...'
rows: 1
---
::
### Loading :badge{label="Not released" class="align-text-top"}
Use the `loading` prop to show a loading icon on the Textarea.
::component-code
---
ignore:
- placeholder
props:
loading: true
trailing: false
placeholder: 'Search...'
rows: 1
---
::
### Loading Icon :badge{label="Not released" class="align-text-top"}
Use the `loading-icon` prop to customize the loading icon. Defaults to `i-lucide-refresh-cw`.
::component-code
---
ignore:
- placeholder
props:
loading: true
loadingIcon: 'i-lucide-repeat-2'
placeholder: 'Search...'
rows: 1
---
::
::framework-only
#nuxt
:::tip{to="/getting-started/icons/nuxt#theme"}
You can customize this icon globally in your `app.config.ts` under `ui.icons.loading` key.
:::
#vue
:::tip{to="/getting-started/icons/vue#theme"}
You can customize this icon globally in your `vite.config.ts` under `ui.icons.loading` key.
:::
::
### Disabled
Use the `disabled` prop to disable the Textarea.
@@ -96,48 +234,6 @@ props:
---
::
### Rows
Use the `rows` prop to set the number of rows. Defaults to `3`.
::component-code
---
props:
rows: 12
---
::
### Autoresize
Use the `autoresize` prop to enable autoresizing the height of the Textarea.
::component-code
---
ignore:
- modelValue
external:
- modelValue
props:
modelValue: 'This is a long text that will autoresize the height of the Textarea.'
autoresize: true
---
::
Use the `maxrows` prop to set the maximum number of rows when autoresizing. If set to `0`, the Textarea will grow indefinitely.
::component-code
---
ignore:
- modelValue
external:
- modelValue
props:
modelValue: 'This is a long text that will autoresize the height of the Textarea with a maximum of 4 rows.'
maxrows: 4
autoresize: true
---
::
## API
### Props

53
docs/content/showcase.yml Normal file
View File

@@ -0,0 +1,53 @@
title: Showcase
description: Check out some of the amazing projects built with Nuxt UI.
navigation: false
hero:
title: Showcase
description: Discover our selection of projects built with Nuxt UI.
items:
- name: Details
url: https://details.team/
- name: Directus Docs
url: https://docs.directus.io/
- name: Espace Asso by Benevolt
url: https://asso.benevolt.fr/
- name: Juno.one
url: https://www.juno.one/
- name: Kassebil
url: https://kassebil.dk/
- name: LearnVue
url: https://learnvue.co/
- name: Mawrble
url: https://mawrble.com/
- name: Meet Sponsors
url: https://meetsponsors.com/
- name: Ovatu
url: https://ovatu.com/
- name: Pallyy
url: https://pallyy.com/
- name: Passionate People
url: https://passionatepeople.io/
- name: Postal
url: https://postalserver.io/
- name: Prismos
url: https://prismos.co/
- name: Readyy
url: https://readyy.app/
- name: Sagematt
url: https://siedlung-sagematt.ch/
- name: Shelve
url: https://shelve.cloud/
- name: Speaker Bot
url: https://speaker.bot/
- name: Super SaaS
url: https://supersaas.dev/
- name: The Companies API
url: https://www.thecompaniesapi.com/
- name: Thuprai
url: https://thuprai.com/
- name: Uneed
url: https://uneed.best/
- name: Wiredash
url: https://wiredash.com/
- name: Zielgestalt
url: https://zielgestalt.de/

56
docs/modules/showcase.ts Normal file
View File

@@ -0,0 +1,56 @@
import { defineNuxtModule } from '@nuxt/kit'
import { existsSync } from 'node:fs'
import { join } from 'pathe'
import captureWebsite from 'capture-website'
interface ContentFile {
id?: string
body?: {
items: TemplateItem[]
}
}
interface TemplateItem {
name: string
url?: string
screenshotUrl?: string
screenshotOptions?: Record<string, any>
}
export default defineNuxtModule((_, nuxt) => {
nuxt.hook('content:file:afterParse', async ({ content: file }: { content: ContentFile }) => {
if (!file.id?.includes('showcase')) {
return
}
if (!file.body?.items?.length) {
return
}
for (const template of file.body.items) {
const url = template.screenshotUrl || template.url
if (!url) {
console.error(`Template ${template.name} has no "url" or "screenshotUrl" to take a screenshot from`)
continue
}
const name = template.name.toLowerCase().replace(/\s/g, '-')
const filename = join(process.cwd(), 'docs/public/assets/showcase', `${name}.png`)
if (existsSync(filename)) {
continue
}
console.log(`Generating screenshot for Template ${template.name} hitting ${url}...`)
try {
await captureWebsite.file(url, filename, {
...(template.screenshotOptions || {}),
launchOptions: { headless: true }
})
console.log(`Screenshot for ${template.name} generated successfully`)
} catch (error) {
console.error(`Error generating screenshot for ${template.name}:`, error)
}
}
})
})

View File

@@ -175,6 +175,10 @@ export default defineNuxtConfig({
}
},
hub: {
ai: true
},
vite: {
plugins: [
yaml()

View File

@@ -3,36 +3,40 @@
"name": "@nuxt/ui-docs",
"type": "module",
"dependencies": {
"@ai-sdk/vue": "^1.2.8",
"@iconify-json/logos": "^1.2.4",
"@iconify-json/lucide": "^1.2.33",
"@iconify-json/simple-icons": "^1.2.29",
"@iconify-json/vscode-icons": "^1.2.17",
"@iconify-json/lucide": "^1.2.36",
"@iconify-json/simple-icons": "^1.2.32",
"@iconify-json/vscode-icons": "^1.2.19",
"@nuxt/content": "^3.4.0",
"@nuxt/image": "^1.10.0",
"@nuxt/ui": "latest",
"@nuxt/ui-pro": "https://pkg.pr.new/@nuxt/ui-pro@63da8be",
"@nuxthub/core": "^0.8.21",
"@nuxt/ui-pro": "https://pkg.pr.new/@nuxt/ui-pro@4757a1e",
"@nuxthub/core": "^0.8.24",
"@nuxtjs/plausible": "^1.2.0",
"@octokit/rest": "^21.1.1",
"@rollup/plugin-yaml": "^4.1.2",
"@vueuse/nuxt": "^13.0.0",
"@vueuse/integrations": "^13.0.0",
"sortablejs": "^1.15.6",
"@vueuse/integrations": "^13.1.0",
"@vueuse/nuxt": "^13.1.0",
"ai": "^4.3.6",
"capture-website": "^4.2.0",
"joi": "^17.13.3",
"motion-v": "0.13.1",
"nuxt": "^3.16.1",
"nuxt": "^3.16.2",
"nuxt-component-meta": "^0.10.1",
"nuxt-llms": "^0.1.2",
"nuxt-og-image": "^5.1.1",
"prettier": "^3.5.3",
"shiki-transformer-color-highlight": "^1.0.0",
"sortablejs": "^1.15.6",
"superstruct": "^2.0.2",
"ufo": "^1.5.4",
"ufo": "^1.6.1",
"valibot": "^1.0.0",
"workers-ai-provider": "^0.3.0",
"yup": "^1.6.1",
"zod": "^3.24.2"
},
"devDependencies": {
"wrangler": "^4.6.0"
"wrangler": "^4.10.0"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 796 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 422 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 558 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 367 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 518 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 749 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 282 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 423 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 350 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 575 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 331 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 645 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

After

Width:  |  Height:  |  Size: 9.0 KiB

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