Compare commits

..

5 Commits

Author SHA1 Message Date
Romain Hamel
3d62371af0 chore: up 2025-03-28 16:22:31 +01:00
Romain Hamel
f941df1541 chore: up 2025-03-28 08:58:21 +01:00
Romain Hamel
664e940098 chore: up 2025-03-26 13:45:17 +01:00
Romain Hamel
15fe0039f0 chore(playground): compodium setup 2025-03-26 11:40:23 +01:00
Romain Hamel
f68061975c chore: setup compodium in playground 2025-03-24 15:08:07 +01:00
387 changed files with 8318 additions and 11883 deletions

View File

@@ -42,8 +42,6 @@ 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, windows-latest] # macos-latest
os: [ubuntu-latest] # macos-latest, windows-latest
node: [22]
env:
@@ -65,7 +65,6 @@ 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:
@@ -161,9 +160,6 @@ jobs:
nuxt-ui-pro:
needs: build
# Only run this job if not a fork PR (when push event or PR from same repo)
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository
runs-on: ${{ matrix.os }}
permissions:

View File

@@ -10,16 +10,14 @@ jobs:
permissions:
issues: write
pull-requests: write
steps:
- uses: actions/stale@v9
with:
days-before-stale: -1 # Issues and PR will never be flagged stale automatically.
stale-issue-label: 'needs reproduction' # Label that flags an issue as stale.
only-labels: 'needs reproduction' # Only process these issues
days-before-issue-close: 7
ignore-updates: true
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
exempt-issue-labels: triage,v3
stale-issue-message: 'This issue is stale because it has been open for 30 days with no activity.'
stale-issue-label: stale
stale-pr-label: stale
days-before-stale: 30
days-before-close: -1

1
.npmrc
View File

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

View File

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

View File

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

View File

@@ -31,10 +31,13 @@ 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 type { ComponentConfig } from '../types/utils'
import { tv } from '${pro ? '#ui/utils/tv' : '../utils/tv'}'
type ${upperName} = ComponentConfig<typeof theme, AppConfig, ${upperName}${pro ? `, '${key}'` : ''}>
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} || {}) })
export interface ${upperName}Props {
/**
@@ -43,7 +46,7 @@ export interface ${upperName}Props {
*/
as?: any
class?: any
ui?: ${upperName}['slots']
ui?: Partial<typeof ${camelName}.slots>
}
export interface ${upperName}Slots {
@@ -52,17 +55,12 @@ 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 appConfig = useAppConfig() as ${upperName}['AppConfig']
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.${camelName} || {}) })())
const ui = ${camelName}()
</script>
<template>
@@ -73,16 +71,22 @@ const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.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 type { ComponentConfig } from '../types/utils'
import { tv } from '${pro ? '#ui/utils/tv' : '../utils/tv'}'
type ${upperName} = ComponentConfig<typeof theme, AppConfig, ${upperName}${pro ? `, '${key}'` : ''}>
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}>
export interface ${upperName}Props extends Pick<${upperName}RootProps> {
class?: any
ui?: ${upperName}['slots']
ui?: Partial<typeof ${camelName}.slots>
}
export interface ${upperName}Emits extends ${upperName}RootEmits {}
@@ -91,21 +95,16 @@ 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 = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.${camelName} || {}) })())
const ui = ${camelName}()
</script>
<template>

View File

@@ -12,7 +12,6 @@ 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 {}')
@@ -65,7 +64,6 @@ provide('navigation', mappedNavigation)
<ClientOnly>
<LazyUContentSearch
:links="searchLinks"
:files="files"
:groups="[{
id: 'framework',
@@ -85,5 +83,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 max-h-[341px] */
/* 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 */
</style>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -12,24 +12,23 @@ function getEmojiFlag(locale: string): string {
ar: 'sa', // Arabic -> Saudi Arabia
bn: 'bd', // Bengali -> Bangladesh
ca: 'es', // Catalan -> Spain
ckb: 'iq', // Central Kurdish -> Iraq
cs: 'cz', // Czech -> Czech Republic (note: modern country code is actually 'cz')
ckb: 'iq', // Central Kurdish -> Iraq
da: 'dk', // Danish -> Denmark
el: 'gr', // Greek -> Greece
en: 'gb', // English -> Great Britain
et: 'ee', // Estonian -> Estonia
en: 'gb', // English -> Great Britain
he: 'il', // Hebrew -> Israel
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
sv: 'se', // Swedish -> Sweden
uk: 'ua', // Ukrainian -> Ukraine
ur: 'pk', // Urdu -> Pakistan
vi: 'vn' // Vietnamese -> Vietnam
vi: 'vn', // Vietnamese -> Vietnam
hy: 'am' // Armenian -> Armenia
}
const baseLanguage = locale.split('-')[0]?.toLowerCase() || locale

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,35 +1,24 @@
<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',
icon: 'i-lucide-ellipsis',
children: [{
label: 'Documentation'
}, {
label: 'Themes'
}, {
label: 'GitHub'
}]
}, {
label: 'Components',
to: '/components'
}, {
label: 'Breadcrumb',
to: '/components/breadcrumb'
}]
</script>
<template>

View File

@@ -1,20 +1,14 @@
<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 = [{
label: 'Home',
to: '/'
}, {
label: 'Components',
to: '/components'
}, {
label: 'Breadcrumb',
to: '/components/breadcrumb'
}]
</script>
<template>

View File

@@ -1,30 +1,21 @@
<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 = [{
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,79 +1,76 @@
<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'
},
{
label: 'Notifications',
icon: 'i-lucide-bell'
},
{
label: 'Security',
icon: 'i-lucide-lock'
}
]
}, {
id: 'users',
label: 'Users',
slot: 'users',
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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -34,10 +34,9 @@ const schema = z.object({
pin: z.string().regex(/^\d$/).array().length(5)
})
type Input = z.input<typeof schema>
type Output = z.output<typeof schema>
type Schema = z.input<typeof schema>
const state = reactive<Partial<Input>>({})
const state = reactive<Partial<Schema>>({})
const form = useTemplateRef('form')
@@ -48,7 +47,7 @@ const items = [
]
const toast = useToast()
async function onSubmit(event: FormSubmitEvent<Output>) {
async function onSubmit(event: FormSubmitEvent<any>) {
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<typeof state>) {
async function onSubmit(event: FormSubmitEvent<any>) {
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<Schema>) {
async function onSubmit(event: FormSubmitEvent<any>) {
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<Schema>) {
<UCheckbox v-model="state.news" name="news" label="Register to our newsletter" @update:model-value="state.email = undefined" />
</div>
<UForm v-if="state.news" :state="state" :schema="nestedSchema" nested>
<UForm v-if="state.news" :state="state" :schema="nestedSchema">
<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<Schema>) {
async function onSubmit(event: FormSubmitEvent<any>) {
toast.add({ title: 'Success', description: 'The form has been submitted.', color: 'success' })
console.log(event.data)
}
@@ -51,14 +51,7 @@ async function onSubmit(event: FormSubmitEvent<Schema>) {
<UInput v-model="state.customer" placeholder="Wonka Industries" />
</UFormField>
<UForm
v-for="item, count in state.items"
:key="count"
:state="item"
:schema="itemSchema"
nested
class="flex gap-2"
>
<UForm v-for="item, count in state.items" :key="count" :state="item" :schema="itemSchema" class="flex gap-2">
<UFormField :label="!count ? 'Description' : undefined" name="description">
<UInput v-model="item.description" />
</UFormField>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,16 +1,17 @@
<script setup lang="ts">
import type { StepperItem } from '@nuxt/ui'
const items: StepperItem[] = [
const items = [
{
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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,32 +1,30 @@
<script setup lang="ts">
import type { TreeItem } from '@nuxt/ui'
const items = [
const items: TreeItem[] = [
{
label: 'app/',
slot: 'app' as const,
slot: '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' }
] satisfies TreeItem[]
]
</script>
<template>

View File

@@ -5,24 +5,22 @@ 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,29 +5,27 @@ 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' }
]
const value = ref()
const value = ref(items[items.length - 1])
</script>
<template>

View File

@@ -8,23 +8,21 @@ 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,23 +8,21 @@ 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 === 'neutral' ? 'old-neutral' : color"
:chip="color"
:selected="neutral === color"
@click="neutral = color"
/>

View File

@@ -84,10 +84,10 @@ export function useLinks() {
label: 'Community',
icon: 'i-lucide-users',
children: [{
icon: 'i-lucide-presentation',
label: 'Showcase',
description: 'Check out some amazing projects built with Nuxt UI.',
to: '/showcase'
label: 'Roadmap',
description: 'Track our development progress in real-time.',
icon: 'i-lucide-map',
to: '/roadmap'
}, {
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

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

View File

@@ -15,7 +15,6 @@ 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 {}')
@@ -67,7 +66,6 @@ provide('navigation', mappedNavigation)
<ClientOnly>
<LazyUContentSearch
:links="searchLinks"
:files="files"
:groups="[{
id: 'framework',

View File

@@ -176,11 +176,7 @@ 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

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

View File

@@ -5,7 +5,6 @@ import { animate } from 'motion-v'
import { joinURL } from 'ufo'
const { url } = useSiteConfig()
useSeoMeta({
title: page.title,
description: page.description,
@@ -156,7 +155,7 @@ onMounted(async () => {
:src="item.src"
:alt="item.alt"
class="w-full h-auto rounded-[calc(var(--ui-radius)*2)]"
loading="lazy"
lazy
/>
</template>
</UTabs>
@@ -166,7 +165,7 @@ onMounted(async () => {
v-if="page.section2.image"
v-bind="page.section2.image"
class="w-full h-auto rounded-[calc(var(--ui-radius)*2)]"
loading="lazy"
lazy
/>
</UPageSection>
<UPageSection v-bind="page.section3" orientation="horizontal" :ui="{ container: 'py-16 sm:pt-16 lg:pt-16' }">
@@ -174,7 +173,7 @@ onMounted(async () => {
v-if="page.section3.image"
v-bind="page.section3.image"
class="w-full h-auto rounded-[calc(var(--ui-radius)*2)]"
loading="lazy"
lazy
/>
</UPageSection>
<USeparator />
@@ -199,7 +198,7 @@ onMounted(async () => {
v-if="step.image"
v-bind="step.image"
class="rounded-(--ui-radius)"
loading="lazy"
lazy
/>
<div>
<h2 class="font-semibold inline-flex items-center gap-x-1">
@@ -273,7 +272,6 @@ 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,7 +23,18 @@ const { data: components } = await useAsyncData('ui-components', () => {
.all()
})
const { data: module } = await useFetch('/api/module.json')
const { data: module } = await useFetch<{
stats: {
downloads: number
stars: number
}
contributors: {
username: string
}[]
}>('https://api.nuxt.com/modules/ui', {
key: 'stats',
transform: ({ stats, contributors }) => ({ stats, contributors })
})
const { format } = Intl.NumberFormat('en', { notation: 'compact' })
@@ -74,7 +85,7 @@ useIntersectionObserver(contributorsRef, ([entry]) => {
</div>
</template>
<LazySkyBg is-index />
<LazySkyBg />
<div class="h-[344px] lg:h-full lg:relative w-full lg:min-h-[calc(100vh-var(--ui-header-height)-1px)] overflow-hidden">
<UPageMarquee
@@ -92,14 +103,10 @@ useIntersectionObserver(contributorsRef, ([entry]) => {
:to="component.path"
>
<UColorModeImage
:light="`${component.path.replace('/components/', '/components/light/')}.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"
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" />
</ULink>
@@ -123,12 +130,7 @@ useIntersectionObserver(contributorsRef, ([entry]) => {
<UColorModeImage
:light="`${component.path.replace('/components/', '/components/light/')}.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"
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" />
</ULink>
@@ -150,9 +152,7 @@ useIntersectionObserver(contributorsRef, ([entry]) => {
:in-view-options="{ once: true }"
class="flex items-start gap-x-3 relative group"
>
<NuxtLink v-if="feature.to" :to="feature.to" class="absolute inset-0 z-10">
<span class="sr-only">Go to {{ feature.title }}</span>
</NuxtLink>
<NuxtLink v-if="feature.to" :to="feature.to" class="absolute inset-0 z-10" />
<div class="relative p-3">
<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="38.5957" cy="37.4551" r="1.5" fill="var(--ui-border-accented)" />
</svg>
<UIcon :name="feature.icon" class="size-5 shrink-0" />
<UIcon :name="feature.icon" class="size-5 flex-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 shrink-0 opacity-0 group-hover:opacity-100 transition-all duration-200 -translate-x-1 group-hover:translate-x-0" />
<UIcon v-if="feature.to" name="i-lucide-arrow-right" class="size-4 flex-shrink-0 opacity-0 group-hover:opacity-100 transition-all duration-200 -translate-x-1 group-hover:translate-x-0" />
</h2>
<p class="text-sm text-(--ui-text-muted)">
{{ feature.description }}
@@ -218,32 +218,26 @@ useIntersectionObserver(contributorsRef, ([entry]) => {
class="border-b border-(--ui-border)"
>
<template #features>
<li>
<NuxtLink to="https://npm.chart.dev/@nuxt/ui" target="_blank" class="min-w-0">
<p class="text-4xl font-semibold text-(--ui-text-highlighted) truncate">
{{ format(module?.stats?.downloads ?? 0) }}+
</p>
<p class="text-(--ui-text-muted) text-sm truncate">monthly downloads</p>
</NuxtLink>
</li>
<NuxtLink to="https://npm.chart.dev/@nuxt/ui" target="_blank" class="min-w-0">
<p class="text-4xl font-semibold text-(--ui-text-highlighted) truncate">
{{ format(module?.stats?.downloads ?? 0) }}+
</p>
<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">
<p class="text-4xl font-semibold text-(--ui-text-highlighted) truncate">
{{ format(module?.stats?.stars ?? 0) }}+
</p>
<p class="text-(--ui-text-muted) text-sm truncate">GitHub stars</p>
</NuxtLink>
</li>
<NuxtLink to="https://github.com/nuxt/ui" target="_blank" class="min-w-0">
<p class="text-4xl font-semibold text-(--ui-text-highlighted) truncate">
{{ format(module?.stats?.stars ?? 0) }}+
</p>
<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">
<p class="text-4xl font-semibold text-(--ui-text-highlighted) truncate">
175+
</p>
<p class="text-(--ui-text-muted) text-sm truncate">Contributors</p>
</NuxtLink>
</li>
<NuxtLink to="https://github.com/nuxt/ui/graphs/contributors" target="_blank" class="min-w-0">
<p class="text-4xl font-semibold text-(--ui-text-highlighted) truncate">
175+
</p>
<p class="text-(--ui-text-muted) text-sm truncate">Contributors</p>
</NuxtLink>
</template>
<div ref="contributorsRef" class="p-4 sm:px-6 md:px-8 lg:px-12 xl:px-14 overflow-hidden flex relative">
@@ -302,8 +296,8 @@ useIntersectionObserver(contributorsRef, ([entry]) => {
:src="`/pro/blocks/image${i}.png`"
width="460"
height="258"
loading="lazy"
:alt="`Nuxt UI Pro Screenshot ${i}`"
loading="lazy"
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,
ogTitle: `${title} - Nuxt UI Pro`,
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<Schema>) {
async function submit(event: FormSubmitEvent<any>) {
activating.value = true
errorMessage.value = ''
successMessage.value = ''

View File

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

View File

@@ -7,8 +7,8 @@ const { url } = useSiteConfig()
useSeoMeta({
title: page.title,
description: page.description,
ogTitle: page.title,
description: page.description,
ogDescription: page.description,
ogImage: joinURL(url, '/pro/og-image.png')
})
@@ -81,7 +81,6 @@ 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

@@ -56,7 +56,6 @@ useSeoMeta({
v-if="template.thumbnail"
v-bind="template.thumbnail"
class="w-full h-auto border lg:border-y lg:border-x-0 border-(--ui-border) rounded-(--ui-radius) lg:rounded-none"
:alt="`Template ${index} thumbnail`"
width="656"
height="369"
loading="lazy"
@@ -67,7 +66,7 @@ useSeoMeta({
:items="(template.images as any[])"
dots
>
<NuxtImg v-bind="item" class="w-full h-full object-cover" width="576" height="360" loading="lazy" />
<NuxtImg v-bind="item" class="w-full h-full object-cover" width="576" height="360" />
</UCarousel>
<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,
ogTitle: `${title} - Nuxt UI Pro`,
ogDescription: description,
ogImage: joinURL(url, '/pro/og-image.png')
})

View File

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

View File

@@ -1,83 +0,0 @@
<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>

View File

@@ -1,156 +0,0 @@
<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://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')
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')
])
}

View File

@@ -1,18 +1,6 @@
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(),
@@ -54,26 +42,5 @@ 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()
})
}))
})
})
}

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