kmcom-nuxt-layers 1.6.18 → 1.6.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/layers/content/app/components/Blog/Article.vue +9 -5
  2. package/layers/content/app/components/Blog/Card.vue +11 -8
  3. package/layers/content/app/components/Blog/List.vue +12 -11
  4. package/layers/content/app/components/Gallery/Card.vue +6 -1
  5. package/layers/content/app/components/Gallery/Detail.vue +10 -6
  6. package/layers/content/app/components/Gallery/ImageDetail.vue +9 -3
  7. package/layers/content/app/components/NuxtContent/Detail.vue +7 -3
  8. package/layers/content/app/components/NuxtContent/Toc.vue +3 -1
  9. package/layers/content/app/components/Portfolio/Card.vue +5 -1
  10. package/layers/content/app/components/Portfolio/Detail.vue +32 -11
  11. package/layers/content/app/components/Portfolio/List.vue +4 -2
  12. package/layers/content/app/composables/useCollectionItem.ts +3 -1
  13. package/layers/content/app/composables/useCollectionSurround.ts +4 -6
  14. package/layers/content/app/types/content.ts +1 -1
  15. package/layers/content/package.json +1 -0
  16. package/layers/core/app/app.vue +1 -0
  17. package/layers/core/app/composables/useCache.ts +7 -8
  18. package/layers/core/app/composables/useScrollGuard.ts +0 -12
  19. package/layers/core/app/plugins/init.ts +4 -3
  20. package/layers/core/package.json +1 -0
  21. package/layers/core/tsconfig.json +2 -1
  22. package/layers/forms/app/components/Form/Contact.vue +1 -5
  23. package/layers/forms/app/components/Form/Field.vue +34 -22
  24. package/layers/forms/package.json +1 -0
  25. package/layers/layout/app/components/Layout/Page/Container.vue +7 -1
  26. package/layers/layout/app/components/Layout/Page/index.vue +4 -1
  27. package/layers/layout/package.json +1 -0
  28. package/layers/motion/app/components/Motion/Marquee.vue +12 -16
  29. package/layers/motion/app/components/Motion/Parallax.vue +1 -1
  30. package/layers/motion/app/components/Motion/Staggered.vue +1 -1
  31. package/layers/motion/app/components/Motion/Transition.vue +0 -13
  32. package/layers/motion/app/composables/useSmoothScroll.ts +1 -1
  33. package/layers/motion/package.json +1 -0
  34. package/layers/routing/app/middleware/02.governance.global.ts +2 -0
  35. package/layers/routing/app/plugins/feature-flags.client.ts +1 -0
  36. package/layers/routing/nuxt.config.ts +8 -4
  37. package/layers/routing/package.json +4 -1
  38. package/layers/shader/app/composables/useShader.ts +1 -1
  39. package/layers/shader/app/utils/tsl/noise.ts +0 -5
  40. package/layers/shader/nuxt.config.ts +1 -1
  41. package/layers/shader/package.json +3 -0
  42. package/layers/theme/package.json +1 -0
  43. package/layers/ui/app/app.config.ts +37 -6
  44. package/layers/ui/app/components/Accent/Scene.vue +14 -1
  45. package/layers/ui/app/components/Mast/NavModal.vue +3 -2
  46. package/layers/ui/app/components/Progress/Bar.vue +11 -1
  47. package/layers/ui/app/components/Typography/CodeBlock.vue +5 -4
  48. package/layers/ui/app/components/Typography/Headline.vue +11 -3
  49. package/layers/ui/app/components/Typography/QuoteBlock.vue +5 -1
  50. package/layers/ui/app/components/Typography/TextStroke.vue +0 -2
  51. package/layers/ui/app/components/Typography/index.vue +1 -1
  52. package/layers/ui/app/composables/gradient.ts +1 -1
  53. package/layers/ui/app/composables/mastNav.ts +0 -1
  54. package/layers/ui/app/composables/toast.ts +4 -4
  55. package/layers/ui/app/composables/useSite.ts +6 -6
  56. package/layers/ui/app/types/accent.ts +1 -1
  57. package/layers/ui/app/types/app-config.d.ts +5 -0
  58. package/layers/ui/app/utils/createModal.ts +4 -4
  59. package/layers/ui/package.json +1 -0
  60. package/package.json +8 -8
@@ -1,25 +1,29 @@
1
1
  <script setup lang="ts">
2
+ import type { BlogCollectionItem } from '@nuxt/content'
3
+
2
4
  const { slug } = defineProps<{
3
5
  slug: string
4
6
  }>()
7
+
8
+ const asBlog = (item: unknown) => item as BlogCollectionItem
5
9
  </script>
6
10
 
7
11
  <template>
8
12
  <NuxtContentDetail collection="blog" :slug not-found-message="Blog post not found">
9
13
  <template #headline="{ item }">
10
14
  <div class="flex items-center gap-3 text-sm text-muted">
11
- <time v-if="item.date">{{ new Date(item.date).toLocaleDateString() }}</time>
12
- <UBadge v-if="item.badge" color="primary" variant="subtle">
13
- {{ item.badge }}
15
+ <time v-if="asBlog(item).date">{{ new Date(asBlog(item).date).toLocaleDateString() }}</time>
16
+ <UBadge v-if="asBlog(item).badge" color="primary" variant="subtle">
17
+ {{ asBlog(item).badge }}
14
18
  </UBadge>
15
19
  </div>
16
20
  </template>
17
21
 
18
22
  <template #after-content="{ item }">
19
- <template v-if="item.tags?.length">
23
+ <template v-if="asBlog(item).tags?.length">
20
24
  <USeparator class="my-8" />
21
25
  <div class="flex flex-wrap gap-2">
22
- <UBadge v-for="tag in item.tags" :key="tag" color="neutral" variant="subtle">
26
+ <UBadge v-for="tag in asBlog(item).tags" :key="tag" color="neutral" variant="subtle">
23
27
  {{ tag }}
24
28
  </UBadge>
25
29
  </div>
@@ -17,24 +17,27 @@ const {
17
17
  authors?: { name: string; avatar?: string; url?: string }[]
18
18
  to?: string
19
19
  }>()
20
+
21
+ const postProps = computed(() => ({
22
+ ...(description !== undefined && { description }),
23
+ ...(date !== undefined && { date }),
24
+ ...(image !== undefined && { image }),
25
+ ...(badge !== undefined && { badge }),
26
+ ...(to !== undefined && { to }),
27
+ }))
20
28
  </script>
21
29
 
22
30
  <template>
23
31
  <UBlogPost
24
32
  :title
25
- :description
26
- :date
27
- :image
28
- :badge
33
+ v-bind="postProps"
29
34
  :authors="
30
35
  authors.map((a) => ({
31
36
  name: a.name,
32
- avatar: a.avatar ? { src: a.avatar } : undefined,
33
- to: a.url,
34
- target: a.url ? '_blank' : undefined,
37
+ ...(a.avatar && { avatar: { src: a.avatar } }),
38
+ ...(a.url && { to: a.url, target: '_blank' as const }),
35
39
  }))
36
40
  "
37
- :to
38
41
  variant="outline"
39
42
  />
40
43
  </template>
@@ -19,20 +19,21 @@ const { data: posts, status } = await useBlogPosts(options)
19
19
  v-for="post in posts"
20
20
  :key="post.id"
21
21
  :title="post.title"
22
- :description="post.description"
23
22
  :date="post.date"
24
- :image="post.image"
25
- :badge="post.badge"
26
- :authors="
27
- post.authors?.map((a) => ({
28
- name: a.name,
29
- avatar: a.avatar ? { src: a.avatar } : undefined,
30
- to: a.url,
31
- target: a.url ? '_blank' : undefined,
32
- }))
33
- "
34
23
  :to="post.path"
35
24
  variant="outline"
25
+ v-bind="{
26
+ ...(post.description !== undefined && { description: post.description }),
27
+ ...(post.image !== undefined && { image: post.image }),
28
+ ...(post.badge !== undefined && { badge: post.badge }),
29
+ ...(post.authors && {
30
+ authors: post.authors.map((a) => ({
31
+ name: a.name,
32
+ ...(a.avatar && { avatar: { src: a.avatar } }),
33
+ ...(a.url && { to: a.url, target: '_blank' as const }),
34
+ })),
35
+ }),
36
+ }"
36
37
  />
37
38
  </UBlogPosts>
38
39
  </NuxtContentList>
@@ -15,10 +15,15 @@ const {
15
15
  tags?: string[]
16
16
  to?: string
17
17
  }>()
18
+
19
+ const cardProps = computed(() => ({
20
+ ...(description !== undefined && { description }),
21
+ ...(to !== undefined && { to }),
22
+ }))
18
23
  </script>
19
24
 
20
25
  <template>
21
- <UPageCard :title :description :to variant="outline">
26
+ <UPageCard :title v-bind="cardProps" variant="outline">
22
27
  <img
23
28
  v-if="coverImage"
24
29
  :src="coverImage.src"
@@ -1,4 +1,6 @@
1
1
  <script setup lang="ts">
2
+ import type { GalleryCollectionItem } from '@nuxt/content'
3
+
2
4
  const { slug } = defineProps<{
3
5
  slug: string
4
6
  }>()
@@ -7,6 +9,8 @@ const lightboxOpen = ref(false)
7
9
  const lightboxIndex = ref(0)
8
10
  const lightboxImages = ref<{ src: string; alt: string; width?: number; height?: number }[]>([])
9
11
 
12
+ const asGallery = (item: unknown) => item as GalleryCollectionItem
13
+
10
14
  function openLightbox(
11
15
  images: { src: string; alt: string; width?: number; height?: number }[],
12
16
  index: number
@@ -21,21 +25,21 @@ function openLightbox(
21
25
  <NuxtContentDetail collection="gallery" :slug not-found-message="Gallery not found">
22
26
  <template #headline="{ item }">
23
27
  <div class="flex flex-wrap items-center gap-2">
24
- <span v-if="item.images?.length" class="text-sm text-muted">
25
- {{ item.images.length }} image{{ item.images.length === 1 ? '' : 's' }}
28
+ <span v-if="asGallery(item).images?.length" class="text-sm text-muted">
29
+ {{ asGallery(item).images!.length }} image{{ asGallery(item).images!.length === 1 ? '' : 's' }}
26
30
  </span>
27
- <UBadge v-for="tag in item.tags" :key="tag" color="neutral" variant="subtle" size="xs">
31
+ <UBadge v-for="tag in asGallery(item).tags" :key="tag" color="neutral" variant="subtle" size="xs">
28
32
  {{ tag }}
29
33
  </UBadge>
30
34
  </div>
31
35
  </template>
32
36
 
33
37
  <template #after-content="{ item }">
34
- <template v-if="item.images?.length">
38
+ <template v-if="asGallery(item).images?.length">
35
39
  <USeparator class="my-8" />
36
40
  <div class="grid grid-cols-2 md:grid-cols-3 gap-4">
37
41
  <div
38
- v-for="(img, idx) in item.images"
42
+ v-for="(img, idx) in asGallery(item).images"
39
43
  :key="img.src"
40
44
  class="overflow-hidden rounded-lg group relative"
41
45
  >
@@ -55,7 +59,7 @@ function openLightbox(
55
59
  </NuxtLink>
56
60
  <button
57
61
  class="absolute top-2 right-2 z-10 opacity-0 group-hover:opacity-100 transition-opacity duration-300 bg-black/50 hover:bg-black/70 text-white rounded-full p-1.5 cursor-pointer"
58
- @click.stop="openLightbox(item.images, idx)"
62
+ @click.stop="openLightbox(asGallery(item).images!, idx)"
59
63
  >
60
64
  <UIcon name="i-lucide-expand" class="size-4" />
61
65
  </button>
@@ -1,10 +1,13 @@
1
1
  <script setup lang="ts">
2
+ import type { GalleryCollectionItem } from '@nuxt/content'
3
+
2
4
  const { slug, index } = defineProps<{
3
5
  slug: string
4
6
  index: number
5
7
  }>()
6
8
 
7
- const { data: item } = await useCollectionItem('gallery', slug)
9
+ const { data: rawItem } = await useCollectionItem('gallery', slug)
10
+ const item = computed(() => rawItem.value as GalleryCollectionItem | null)
8
11
 
9
12
  const image = computed(() => item.value?.images?.[index])
10
13
  const totalImages = computed(() => item.value?.images?.length ?? 0)
@@ -19,8 +22,11 @@ useSeoMeta({
19
22
  </script>
20
23
 
21
24
  <template>
22
- <div v-if="item && image">
23
- <UPageHeader :title="image.title || image.alt" :description="image.caption">
25
+ <div v-if="item && item.images && image">
26
+ <UPageHeader
27
+ :title="image.title || image.alt"
28
+ v-bind="image.caption ? { description: image.caption } : {}"
29
+ >
24
30
  <template #headline>
25
31
  <UBreadcrumb
26
32
  :items="[
@@ -15,10 +15,14 @@ const {
15
15
  const { data: item, status } = await useCollectionItem(collection, slug)
16
16
  const { data: surround } = await useCollectionSurround(collection, slug)
17
17
 
18
+ const itemImage = computed(
19
+ () => (item.value as { image?: string } | null)?.image
20
+ )
21
+
18
22
  useSeoMeta({
19
23
  title: item.value?.title,
20
24
  description: item.value?.description,
21
- ogImage: item.value?.image,
25
+ ogImage: itemImage,
22
26
  })
23
27
  </script>
24
28
 
@@ -26,7 +30,7 @@ useSeoMeta({
26
30
  <div>
27
31
  <USkeleton v-if="status === 'pending'" class="h-96 w-full" />
28
32
  <UPage v-else-if="item">
29
- <UPageHeader :title="item.title" :description="item.description">
33
+ <UPageHeader :title="item.title" v-bind="item.description ? { description: item.description } : {}">
30
34
  <template v-if="$slots.headline" #headline>
31
35
  <slot name="headline" :item="item" />
32
36
  </template>
@@ -45,7 +49,7 @@ useSeoMeta({
45
49
  </UPageBody>
46
50
 
47
51
  <template v-if="!hideToc" #right>
48
- <NuxtContentToc :links="item.body?.toc?.links" />
52
+ <NuxtContentToc v-bind="item.body?.toc?.links ? { links: item.body.toc.links } : {}" />
49
53
  </template>
50
54
  </UPage>
51
55
  <div v-else class="text-muted text-center py-12">{{ notFoundMessage }}</div>
@@ -1,7 +1,9 @@
1
1
  <!-- eslint-disable vue/require-default-prop -->
2
2
  <script setup lang="ts">
3
+ import type { TocLink } from '@nuxt/content'
4
+
3
5
  const { links, title = 'Table of Contents' } = defineProps<{
4
- links?: { id: string; text: string; children?: { id: string; text: string }[] }[]
6
+ links?: TocLink[]
5
7
  title?: string
6
8
  }>()
7
9
  </script>
@@ -20,7 +20,11 @@ const {
20
20
  </script>
21
21
 
22
22
  <template>
23
- <UPageCard :title :description :to variant="outline">
23
+ <UPageCard
24
+ :title
25
+ variant="outline"
26
+ v-bind="{ ...(description !== undefined && { description }), ...(to !== undefined && { to }) }"
27
+ >
24
28
  <img v-if="image" :src="image" :alt="title" class="w-full h-48 object-cover rounded" />
25
29
  <template #footer>
26
30
  <div class="flex flex-wrap gap-1.5">
@@ -1,7 +1,11 @@
1
1
  <script setup lang="ts">
2
+ import type { PortfolioCollectionItem } from '@nuxt/content'
3
+
2
4
  const { slug } = defineProps<{
3
5
  slug: string
4
6
  }>()
7
+
8
+ const asPortfolio = (item: unknown) => item as PortfolioCollectionItem
5
9
  </script>
6
10
 
7
11
  <template>
@@ -13,13 +17,19 @@ const { slug } = defineProps<{
13
17
  >
14
18
  <template #headline="{ item }">
15
19
  <div class="flex flex-wrap items-center gap-2">
16
- <UBadge v-if="item.client" color="primary" variant="subtle">
17
- {{ item.client }}
20
+ <UBadge v-if="asPortfolio(item).client" color="primary" variant="subtle">
21
+ {{ asPortfolio(item).client }}
18
22
  </UBadge>
19
- <UBadge v-if="item.year" color="neutral" variant="subtle">
20
- {{ item.year }}
23
+ <UBadge v-if="asPortfolio(item).year" color="neutral" variant="subtle">
24
+ {{ asPortfolio(item).year }}
21
25
  </UBadge>
22
- <UBadge v-for="tag in item.tags" :key="tag" color="neutral" variant="subtle" size="xs">
26
+ <UBadge
27
+ v-for="tag in asPortfolio(item).tags"
28
+ :key="tag"
29
+ color="neutral"
30
+ variant="subtle"
31
+ size="xs"
32
+ >
23
33
  {{ tag }}
24
34
  </UBadge>
25
35
  </div>
@@ -27,8 +37,8 @@ const { slug } = defineProps<{
27
37
 
28
38
  <template #links="{ item }">
29
39
  <UButton
30
- v-if="item.url"
31
- :to="item.url"
40
+ v-if="asPortfolio(item).url"
41
+ :to="asPortfolio(item).url"
32
42
  target="_blank"
33
43
  icon="i-lucide-external-link"
34
44
  variant="outline"
@@ -38,15 +48,26 @@ const { slug } = defineProps<{
38
48
  </template>
39
49
 
40
50
  <template #before-content="{ item }">
41
- <img v-if="item.image" :src="item.image" :alt="item.title" class="w-full rounded-lg mb-8" />
51
+ <img
52
+ v-if="asPortfolio(item).image"
53
+ :src="asPortfolio(item).image"
54
+ :alt="item.title"
55
+ class="w-full rounded-lg mb-8"
56
+ />
42
57
  </template>
43
58
 
44
59
  <template #after-content="{ item }">
45
- <template v-if="item.colors?.length || item.typography?.length">
60
+ <template v-if="asPortfolio(item).colors?.length || asPortfolio(item).typography?.length">
46
61
  <USeparator class="my-8" />
47
62
  <div class="space-y-8">
48
- <PortfolioColorPalette v-if="item.colors?.length" :colors="item.colors" />
49
- <PortfolioTypography v-if="item.typography?.length" :typography="item.typography" />
63
+ <PortfolioColorPalette
64
+ v-if="asPortfolio(item).colors?.length"
65
+ :colors="asPortfolio(item).colors!"
66
+ />
67
+ <PortfolioTypography
68
+ v-if="asPortfolio(item).typography?.length"
69
+ :typography="asPortfolio(item).typography!"
70
+ />
50
71
  </div>
51
72
  </template>
52
73
  </template>
@@ -19,9 +19,11 @@ const { data: items, status } = await usePortfolioItems(options)
19
19
  v-for="item in items"
20
20
  :key="item.id"
21
21
  :title="item.title"
22
- :description="item.description"
23
- :to="item.path"
24
22
  variant="outline"
23
+ v-bind="{
24
+ ...(item.description !== undefined && { description: item.description }),
25
+ to: item.path,
26
+ }"
25
27
  >
26
28
  <img
27
29
  v-if="item.image"
@@ -1,5 +1,7 @@
1
+ import type { Collections } from '@nuxt/content'
2
+
1
3
  export function useCollectionItem(collection: string, slug: string) {
2
4
  return useContentData(`${collection}-${slug}`, () =>
3
- queryCollection(collection).path(`/${collection}/${slug}`).first()
5
+ queryCollection(collection as keyof Collections).path(`/${collection}/${slug}`).first()
4
6
  )
5
7
  }
@@ -1,9 +1,7 @@
1
- export function useCollectionSurround(
2
- collection: string,
3
- slug: string,
4
- fields: string[] = ['description']
5
- ) {
1
+ import type { PageCollections } from '@nuxt/content'
2
+
3
+ export function useCollectionSurround(collection: string, slug: string) {
6
4
  return useContentData(`${collection}-${slug}-surround`, () =>
7
- queryCollectionItemSurroundings(collection, `/${collection}/${slug}`, { fields })
5
+ queryCollectionItemSurroundings(collection as keyof PageCollections, `/${collection}/${slug}`)
8
6
  )
9
7
  }
@@ -22,7 +22,7 @@ export interface PortfolioColor {
22
22
 
23
23
  export interface PortfolioFont {
24
24
  name: string
25
- weights: string[]
25
+ weights?: string[]
26
26
  usage?: string
27
27
  }
28
28
 
@@ -4,6 +4,7 @@
4
4
  "version": "0.0.1",
5
5
  "main": "./nuxt.config.ts",
6
6
  "scripts": {
7
+ "typecheck": "vue-tsc --noEmit -p ../../tsconfig.typecheck.json",
7
8
  "dev": "nuxi dev .playground",
8
9
  "dev:content": "CONTENT_STANDALONE=true nuxi dev",
9
10
  "dev:prepare": "nuxt prepare .playground",
@@ -3,5 +3,6 @@
3
3
  <NuxtLayout>
4
4
  <NuxtPage />
5
5
  </NuxtLayout>
6
+ <UOverlayProvider />
6
7
  </UApp>
7
8
  </template>
@@ -78,15 +78,14 @@ export function useCache() {
78
78
 
79
79
  try {
80
80
  const cacheNames = await caches.keys()
81
- ;(await Promise.all(cacheNames.map((name) => caches.delete(name)))(
82
- process.env.NODE_ENV === 'development'
83
- ))
84
- ? console.log('[useCache] All caches cleared')
85
- : ''
81
+ await Promise.all(cacheNames.map((name) => caches.delete(name)))
82
+ if (process.env.NODE_ENV === 'development') {
83
+ console.log('[useCache] All caches cleared')
84
+ }
86
85
  } catch (error) {
87
- process.env.NODE_ENV === 'development'
88
- ? console.error('[useCache] Failed to clear cache:', error)
89
- : ''
86
+ if (process.env.NODE_ENV === 'development') {
87
+ console.error('[useCache] Failed to clear cache:', error)
88
+ }
90
89
  }
91
90
  }
92
91
 
@@ -77,18 +77,6 @@ function clampElement(el: HTMLElement) {
77
77
  clampedCount.value++
78
78
  }
79
79
 
80
- function restoreElement(el: HTMLElement) {
81
- const saved = clampedElements.get(el)
82
- if (!saved) return
83
-
84
- el.style.transition = saved.transition
85
- el.style.maxWidth = saved.maxWidth
86
- el.style.boxSizing = saved.boxSizing
87
- el.style.overflowX = saved.overflowX
88
-
89
- clampedElements.delete(el)
90
- clampedSet.delete(el)
91
- }
92
80
 
93
81
  function guard() {
94
82
  if (!isEnabled.value || !opts) return
@@ -130,13 +130,14 @@ export default defineNuxtPlugin((nuxtApp) => {
130
130
  }
131
131
 
132
132
  // Test environment access (works on both server and client)
133
- const env = useEnv()
133
+ const env = useEnv() as unknown as Record<string, unknown>
134
134
 
135
135
  if (isDev) {
136
+ const publicConfig = env.public as Record<string, unknown> | undefined
136
137
  // eslint-disable-next-line no-console
137
138
  console.log('[Core Layer] Environment config loaded:', {
138
- hasPublicConfig: !!env.public,
139
- publicKeys: Object.keys(env.public || {}),
139
+ hasPublicConfig: !!publicConfig,
140
+ publicKeys: Object.keys(publicConfig ?? {}),
140
141
  })
141
142
  }
142
143
  } catch (error) {
@@ -4,6 +4,7 @@
4
4
  "type": "module",
5
5
  "main": "./nuxt.config.ts",
6
6
  "scripts": {
7
+ "typecheck": "vue-tsc --noEmit -p ../../tsconfig.typecheck.json",
7
8
  "dev": "nuxi dev .playground",
8
9
  "dev:prepare": "nuxt prepare .playground",
9
10
  "build": "nuxt build .playground",
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "extends": "../../apps/playground/tsconfig.json",
3
3
  "compilerOptions": {
4
- "strict": true
4
+ "strict": true,
5
+ "ignoreDeprecations": "6.0"
5
6
  }
6
7
  }
@@ -17,11 +17,7 @@ const schema = z.object({
17
17
 
18
18
  type FormState = z.infer<typeof schema>
19
19
 
20
- const state = reactive<Partial<FormState>>({
21
- name: undefined,
22
- email: undefined,
23
- message: undefined,
24
- })
20
+ const state = reactive({ name: '', email: '', message: '' })
25
21
 
26
22
  const toast = useToast()
27
23
  const isLoading = ref(false)
@@ -41,6 +41,36 @@ const resolvedIcon = computed(() => icon ?? config.value.icon)
41
41
  const isTextarea = computed(() => config.value.component === 'UTextarea')
42
42
  const isNumber = computed(() => config.value.component === 'UInputNumber')
43
43
 
44
+ const formFieldProps = computed(() => ({
45
+ name,
46
+ required,
47
+ size,
48
+ ...(label !== undefined && { label }),
49
+ ...(className !== undefined && { class: className }),
50
+ }))
51
+
52
+ const baseInputProps = computed(() => ({
53
+ size,
54
+ ...(resolvedPlaceholder.value !== undefined && { placeholder: resolvedPlaceholder.value }),
55
+ ...(resolvedIcon.value !== undefined && { leadingIcon: resolvedIcon.value }),
56
+ }))
57
+
58
+ const numberInputProps = computed(() => ({
59
+ size,
60
+ ...(resolvedPlaceholder.value !== undefined && { placeholder: resolvedPlaceholder.value }),
61
+ ...(resolvedIcon.value !== undefined && { leadingIcon: resolvedIcon.value }),
62
+ ...(currencyOptions.value !== undefined && { formatOptions: currencyOptions.value }),
63
+ }))
64
+
65
+ const textInputProps = computed(() => ({
66
+ type: config.value.inputType,
67
+ size,
68
+ ...(config.value.inputMode !== undefined && { inputmode: config.value.inputMode }),
69
+ ...(config.value.autocomplete !== undefined && { autocomplete: config.value.autocomplete }),
70
+ ...(resolvedPlaceholder.value !== undefined && { placeholder: resolvedPlaceholder.value }),
71
+ ...(resolvedIcon.value !== undefined && { leadingIcon: resolvedIcon.value }),
72
+ }))
73
+
44
74
  const currencyOptions = computed((): Intl.NumberFormatOptions | undefined => {
45
75
  if (config.value.format === 'currency') {
46
76
  return {
@@ -53,33 +83,15 @@ const currencyOptions = computed((): Intl.NumberFormatOptions | undefined => {
53
83
  </script>
54
84
 
55
85
  <template>
56
- <UFormField :name :label :required :size :class="className">
57
- <UTextarea
58
- v-if="isTextarea"
59
- v-model="model as string"
60
- :placeholder="resolvedPlaceholder"
61
- :size
62
- autoresize
63
- />
86
+ <UFormField v-bind="formFieldProps">
87
+ <UTextarea v-if="isTextarea" v-model="model as string" autoresize v-bind="baseInputProps" />
64
88
 
65
89
  <UInputNumber
66
90
  v-else-if="isNumber"
67
91
  v-model="model as number"
68
- :placeholder="resolvedPlaceholder"
69
- :leading-icon="resolvedIcon"
70
- :size
71
- :format-options="currencyOptions"
92
+ v-bind="numberInputProps"
72
93
  />
73
94
 
74
- <UInput
75
- v-else
76
- v-model="model as string"
77
- :type="config.inputType"
78
- :inputmode="config.inputMode"
79
- :autocomplete="config.autocomplete"
80
- :placeholder="resolvedPlaceholder"
81
- :leading-icon="resolvedIcon"
82
- :size
83
- />
95
+ <UInput v-else v-model="model as string" v-bind="textInputProps" />
84
96
  </UFormField>
85
97
  </template>
@@ -4,6 +4,7 @@
4
4
  "type": "module",
5
5
  "main": "./nuxt.config.ts",
6
6
  "scripts": {
7
+ "typecheck": "vue-tsc --noEmit -p ../../tsconfig.typecheck.json",
7
8
  "dev": "nuxi dev .playground",
8
9
  "dev:prepare": "nuxt prepare .playground",
9
10
  "build": "nuxt build .playground",
@@ -78,7 +78,13 @@ provide('pageTitle', title)
78
78
  <!-- Optional visible header -->
79
79
  <LayoutSection v-if="showHeader">
80
80
  <LayoutGridItem :preset="headerPreset">
81
- <LayoutPageHeader :title :description :back />
81
+ <LayoutPageHeader
82
+ :title
83
+ v-bind="{
84
+ ...(description !== undefined && { description }),
85
+ ...(back !== undefined && { back }),
86
+ }"
87
+ />
82
88
  </LayoutGridItem>
83
89
  </LayoutSection>
84
90
 
@@ -60,7 +60,10 @@ provide('pageTitle', title)
60
60
  <!-- Optional visible page header — rendered as a grid section -->
61
61
  <LayoutSection v-if="showHeader">
62
62
  <LayoutGridItem preset="centered">
63
- <LayoutPageHeader :title :description />
63
+ <LayoutPageHeader
64
+ :title
65
+ v-bind="description !== undefined ? { description } : {}"
66
+ />
64
67
  </LayoutGridItem>
65
68
  </LayoutSection>
66
69
 
@@ -4,6 +4,7 @@
4
4
  "version": "0.0.1",
5
5
  "main": "./nuxt.config.ts",
6
6
  "scripts": {
7
+ "typecheck": "vue-tsc --noEmit -p ../../tsconfig.typecheck.json",
7
8
  "dev": "nuxi dev .playground",
8
9
  "dev:prepare": "nuxt prepare .playground",
9
10
  "build": "nuxt build .playground",