Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ defineProps<{

<ul class="flex gap-x-1 flex-wrap gap-2">
<li v-for="achievement in achievements" :key="achievement.label">
<UBadge v-bind="achievement" variant="subtle" />
<UBadge :label="achievement.label" :color="achievement.color" variant="subtle" />
</li>
</ul>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ async function onSubmit(event: FormSubmitEvent<Schema>) {

<div class="flex justify-end">
<UButton
v-bind="formProps.form.button"
v-bind="pickLink(formProps.form.button)"
type="submit"
color="neutral"
class="w-fit pt-2"
Expand Down
2 changes: 1 addition & 1 deletion app/pages/blog/[slug].vue
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ const links = [
</template>

<div class="mt-4 flex flex-wrap items-center gap-6">
<UUser v-for="(author, index) in articleData.authors" :key="index" v-bind="author" :description="author.to ? `@${author.to.split('/').pop()}` : undefined" />
<UUser v-for="(author, index) in articleData.authors" :key="index" v-bind="pickUser(author)" :description="author.to ? `@${author.to.split('/').pop()}` : undefined" />
</div>
</UPageHeader>

Expand Down
2 changes: 1 addition & 1 deletion app/pages/blog/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ await fetchList()
alt: `${article.title} image`
}"
:date="formatDateByLocale('en', article.date)"
:authors="article.authors.map(author => ({ ...author, avatar: { ...author.avatar, alt: `${author.name} avatar` } }))"
:authors="article.authors.map(author => pickUser({ ...author, avatar: { ...author.avatar, alt: `${author.name} avatar` } }))"
:badge="{ label: article.category, color: 'primary', variant: 'subtle' }"
:variant="index === 0 ? 'outline' : 'subtle'"
:orientation="index === 0 ? 'horizontal' : 'vertical'"
Expand Down
2 changes: 1 addition & 1 deletion app/pages/design-kit.vue
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ defineOgImage('Docs.takumi', {
<UPage v-if="pageData">
<UPageHero
:title="pageData.title"
:links="pageData.links"
:links="pickLinks(pageData.links)"
:description="pageData.description"
:ui="{
title: 'text-left',
Expand Down
7 changes: 4 additions & 3 deletions app/pages/docs/[...slug].vue
Original file line number Diff line number Diff line change
Expand Up @@ -312,10 +312,11 @@ const noRightAside = computed(() => route.path.includes('/examples/'))
</UPageAside>
</template>
<UPageHeader
:title="fm.title"
:description="fm.description"
:ui="{
wrapper: 'flex-row items-center flex-wrap justify-between'
}"
v-bind="fm"
>
<template #headline>
<UBreadcrumb :items="breadcrumb" />
Expand All @@ -342,10 +343,10 @@ const noRightAside = computed(() => route.path.includes('/examples/'))
color="neutral"
variant="soft"
:target="link.to.startsWith('http') ? '_blank' : undefined"
v-bind="{ ...link, size: 'sm' }"
v-bind="{ ...pickLink(link), size: 'sm' }"
>
<template v-if="link.avatar" #leading>
<UAvatar v-bind="link.avatar" size="2xs" :alt="`${link.label} avatar`" />
<UAvatar v-bind="pickAvatar(link.avatar)" size="2xs" :alt="`${link.label} avatar`" />
</template>
</UButton>
<PageHeaderLinks :key="page.path" />
Expand Down
2 changes: 1 addition & 1 deletion app/pages/enterprise/agencies/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ await fetchList()
<UPageHero
:title="pageData.title"
:description="pageData.description"
:links="pageData.links"
:links="pickLinks(pageData.links)"
/>

<UContainer>
Expand Down
2 changes: 1 addition & 1 deletion app/pages/enterprise/jobs.vue
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ await fetchList()
<UPageHero
:title="pageData.title"
:description="pageData.description"
:links="pageData.links"
:links="pickLinks(pageData.links)"
:ui="{
title: 'text-left',
description: 'text-left',
Expand Down
2 changes: 1 addition & 1 deletion app/pages/enterprise/sponsors.vue
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ defineOgImage('Docs.takumi', {
<UPageHero
:title="pageData.title"
:description="pageData.description"
:links="pageData.links"
:links="pickLinks(pageData.links)"
/>

<UPageBody class="relative">
Expand Down
29 changes: 21 additions & 8 deletions app/pages/enterprise/support.vue
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,24 @@ useCanonical()

<template>
<UPage v-if="pageData">
<UPageHero :title="pageData.title" :description="pageData.description" :links="pageData.hero.links">
<UPageHero :title="pageData.title" :description="pageData.description" :links="pickLinks(pageData.hero.links)">
<UPageLogos marquee>
<UColorModeImage
v-for="company in pageData.logos"
:key="company.alt"
v-bind="company"
:light="company.light"
:dark="company.dark"
:width="company.width"
:height="company.height"
:alt="company.alt"
class="h-10 max-w-[120px] object-contain"
/>
</UPageLogos>
</UPageHero>

<UPageSection
v-bind="pageData.service"
:title="pageData.service.title"
:description="pageData.service.description"
:ui="{
title: 'text-left',
description: 'text-left',
Expand All @@ -47,20 +52,27 @@ useCanonical()
>
<template #links>
<UPageGrid class="xl:grid-cols-4">
<UPageCard v-for="service in pageData.service.services" :key="service.title" v-bind="service" :ui="{ container: 'card-bg', leadingIcon: 'size-8' }" />
<UPageCard
v-for="service in pageData.service.services"
:key="service.title"
:icon="service.icon"
:title="service.title"
:description="service.description"
:ui="{ container: 'card-bg', leadingIcon: 'size-8' }"
/>
</UPageGrid>
</template>
</UPageSection>

<UPageSection v-bind="pageData.expertise">
<UPageSection :title="pageData.expertise.title" :description="pageData.expertise.description">
<div class="flex justify-center w-full gap-4 sm:gap-x-16 md:gap-x-[92px]">
<div v-for="logo in pageData.expertise.logos" :key="logo.src" class="flex items-center justify-center">
<EnterpriseSupportExpertiseCircle :logo="logo" />
</div>
</div>
</UPageSection>

<UPageSection v-bind="pageData.testimonials">
<UPageSection :title="pageData.testimonials.title" :description="pageData.testimonials.description">
<UCarousel
v-slot="{ item }"
:items="(pageData.testimonials.items as any[])"
Expand All @@ -74,12 +86,13 @@ useCanonical()
arrows: 'hidden 2xl:block'
}"
>
<EnterpriseSupportClientCard v-bind="item" />
<EnterpriseSupportClientCard :quote="item.quote" :author="item.author" :job="item.job" :logo="item.logo" :achievements="item.achievements" />
</UCarousel>
</UPageSection>

<UPageSection
v-bind="pageData.project"
:title="pageData.project.title"
:description="pageData.project.description"
orientation="horizontal"
:ui="{
links: 'mt-8 flex flex-wrap justify-center lg:justify-start gap-x-3 gap-y-1.5',
Expand Down
14 changes: 7 additions & 7 deletions app/pages/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ const isMobile = useMediaQuery('(max-width: 1023.98px)')
:in-view-options="{ once: true }"
>
<UPageFeature
v-bind="feature"
v-bind="pickFeature(feature)"
orientation="vertical"
/>
</Motion>
Expand Down Expand Up @@ -298,7 +298,7 @@ const isMobile = useMediaQuery('(max-width: 1023.98px)')
}"
>
<UUser
v-bind="pageData.testimonial.author"
v-bind="pickUser(pageData.testimonial.author)"
size="xl"
class="justify-center"
/>
Expand Down Expand Up @@ -427,7 +427,7 @@ const isMobile = useMediaQuery('(max-width: 1023.98px)')
<p class="text-muted text-center">
{{ pageData.stats.community.description }}
</p>
<UButton class="mt-4 w-fit" v-bind="pageData.stats.cta" />
<UButton class="mt-4 w-fit" v-bind="pickLink(pageData.stats.cta)" />
</div>
</UPageCard>
</div>
Expand Down Expand Up @@ -466,7 +466,7 @@ const isMobile = useMediaQuery('(max-width: 1023.98px)')

<UPageSection
:description="pageData.modules.description"
:links="pageData.modules.links"
:links="pickLinks(pageData.modules.links)"
:ui="{
root: 'bg-linear-to-b border-t border-default from-muted dark:from-muted/40 to-default',
title: 'text-left',
Expand Down Expand Up @@ -498,7 +498,7 @@ const isMobile = useMediaQuery('(max-width: 1023.98px)')
<UPageSection
:title="pageData.deploy.title"
:description="pageData.deploy.description"
:links="pageData.deploy.links"
:links="pickLinks(pageData.deploy.links)"
orientation="horizontal"
:ui="{
root: 'bg-linear-to-b border-t border-default from-muted dark:from-muted/40 to-default'
Expand All @@ -517,7 +517,7 @@ const isMobile = useMediaQuery('(max-width: 1023.98px)')
<UPageSection
:title="pageData.contributors.title"
:description="pageData.contributors.description"
:links="pageData.contributors.links"
:links="pickLinks(pageData.contributors.links)"
orientation="horizontal"
reverse
:ui="{
Expand Down Expand Up @@ -593,7 +593,7 @@ const isMobile = useMediaQuery('(max-width: 1023.98px)')
<UPageSection
:title="pageData.sponsors.title"
:description="pageData.sponsors.description"
:links="pageData.sponsors.links"
:links="pickLinks(pageData.sponsors.links)"
class="relative"
:ui="{
root: 'bg-linear-to-b border-t border-default from-muted dark:from-muted/40 to-default',
Expand Down
7 changes: 6 additions & 1 deletion app/pages/newsletter.vue
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,12 @@ defineOgImage('Docs.takumi', {

<UPageBody>
<UContainer>
<UPageCTA v-bind="pageData.cta" />
<UPageCTA
:headline="pageData.cta.headline"
:title="pageData.cta.title"
:description="pageData.cta.description"
:links="pickLinks(pageData.cta.links)"
/>
</UContainer>
</UPageBody>
</UPage>
Expand Down
2 changes: 1 addition & 1 deletion app/pages/templates.vue
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ defineOgImage('Docs.takumi', {
<UPageHero
:title="pageData.title"
:description="pageData.description"
:links="pageData.links"
:links="pickLinks(pageData.links)"
/>
<UPageBody>
<UContainer>
Expand Down
2 changes: 1 addition & 1 deletion app/pages/video-courses.vue
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ defineOgImage('Docs.takumi', {
<UPageHero
:title="pageData.title"
:description="pageData.description"
:links="pageData.links"
:links="pickLinks(pageData.links)"
/>
<UPageBody>
<UContainer>
Expand Down
48 changes: 48 additions & 0 deletions app/utils/content.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { NavigationItem } from 'comark-content'
import type { AvatarProps, ButtonProps, PageFeatureProps, UserProps } from '@nuxt/ui'

export function navPageFromPath(path: string, tree: NavigationItem[]): NavigationItem | undefined {
for (const file of tree) {
Expand Down Expand Up @@ -96,3 +97,50 @@ export function listSurround(

return [link(items?.[index - 1]), link(items?.[index + 1])]
}

/**
* Content data reaches components through these picks, never a `v-bind` spread.
* - Vue binds every unknown key to the DOM, so a spread lets content set any attribute.
* - Nuxt UI spreads each `links` item onto a `UButton`, each `authors` item onto a `UUser`…
* Keys are the ones content uses: add one here before content can set it.
*/

type ContentObject = Record<string, any> | null | undefined

function pick<T>(data: ContentObject, keys: readonly string[]): T {
const picked: Record<string, unknown> = {}
for (const key of keys) {
if (data?.[key] !== undefined) picked[key] = data[key]
}
return picked as T
}

const AVATAR_KEYS = ['src', 'srcset', 'alt', 'text', 'icon', 'provider', 'loading']
const LINK_KEYS = ['label', 'to', 'target', 'title', 'icon', 'leadingIcon', 'trailingIcon', 'trailing', 'color', 'variant', 'size', 'download', 'external']
const FEATURE_KEYS = ['title', 'description', 'icon', 'to', 'target']
const USER_KEYS = ['name', 'description', 'to', 'target']

export function pickAvatar(avatar: ContentObject): AvatarProps | undefined {
return avatar ? pick<AvatarProps>(avatar, AVATAR_KEYS) : undefined
}

export function pickLink(link: ContentObject): ButtonProps {
const picked = pick<ButtonProps>(link, LINK_KEYS)
if (link?.avatar) picked.avatar = pickAvatar(link.avatar)
return picked
}

/** `undefined` stays `undefined`, so components still skip an absent `links`. */
export function pickLinks(links: ContentObject[] | null | undefined): ButtonProps[] | undefined {
return links?.map(pickLink)
}

export function pickFeature(feature: ContentObject): PageFeatureProps {
return pick<PageFeatureProps>(feature, FEATURE_KEYS)
}

export function pickUser(user: ContentObject): UserProps {
const picked = pick<UserProps>(user, USER_KEYS)
if (user?.avatar) picked.avatar = pickAvatar(user.avatar)
return picked
}
Loading
Loading