@duffcloudservices/cms 0.3.12 → 0.3.14

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.
@@ -1,55 +1,55 @@
1
- <script setup lang="ts">
2
- /**
3
- * Convenience component that renders a responsive `<picture>` element for
4
- * DCS CDN-hosted images. For non-CDN images it falls back to a plain `<img>`.
5
- *
6
- * @example
7
- * ```vue
8
- * <ResponsiveImage
9
- * src="https://files.duffcloudservices.com/kept/assets/hero/abc-123.jpg"
10
- * alt="Hero banner"
11
- * context="hero"
12
- * class="w-full h-full object-cover"
13
- * />
14
- * ```
15
- */
16
- import { useResponsiveImage } from '../composables/useResponsiveImage'
17
- import type { ImageContext } from '@duffcloudservices/cms-core'
18
-
19
- const props = defineProps<{
20
- /** Image source URL (original CDN URL or local path). */
21
- src: string
22
- /** Alt text for accessibility. */
23
- alt: string
24
- /** Sizing context — determines which variants to include. */
25
- context?: ImageContext
26
- /** CSS class(es) applied to the `<img>` element. */
27
- class?: string
28
- /** Optional `sizes` attribute override. */
29
- sizes?: string
30
- /** Skip responsive variants and use the original URL only. */
31
- original?: boolean
32
- }>()
33
-
34
- const image = useResponsiveImage({
35
- src: () => props.src,
36
- alt: () => props.alt,
37
- context: () => props.context ?? 'content',
38
- sizes: () => props.sizes,
39
- original: () => props.original,
40
- })
41
- </script>
42
-
43
- <template>
44
- <picture v-if="image.hasVariants && !original">
45
- <source
46
- v-for="source in image.sources"
47
- :key="source.type"
48
- :srcset="source.srcset"
49
- :type="source.type"
50
- :sizes="source.sizes"
51
- />
52
- <img v-bind="image.imgProps" :class="props.class" />
53
- </picture>
54
- <img v-else v-bind="image.imgProps" :class="props.class" />
55
- </template>
1
+ <script setup lang="ts">
2
+ /**
3
+ * Convenience component that renders a responsive `<picture>` element for
4
+ * DCS CDN-hosted images. For non-CDN images it falls back to a plain `<img>`.
5
+ *
6
+ * @example
7
+ * ```vue
8
+ * <ResponsiveImage
9
+ * src="https://files.duffcloudservices.com/kept/assets/hero/abc-123.jpg"
10
+ * alt="Hero banner"
11
+ * context="hero"
12
+ * class="w-full h-full object-cover"
13
+ * />
14
+ * ```
15
+ */
16
+ import { useResponsiveImage } from '../composables/useResponsiveImage'
17
+ import type { ImageContext } from '@duffcloudservices/cms-core'
18
+
19
+ const props = defineProps<{
20
+ /** Image source URL (original CDN URL or local path). */
21
+ src: string
22
+ /** Alt text for accessibility. */
23
+ alt: string
24
+ /** Sizing context — determines which variants to include. */
25
+ context?: ImageContext
26
+ /** CSS class(es) applied to the `<img>` element. */
27
+ class?: string
28
+ /** Optional `sizes` attribute override. */
29
+ sizes?: string
30
+ /** Skip responsive variants and use the original URL only. */
31
+ original?: boolean
32
+ }>()
33
+
34
+ const image = useResponsiveImage({
35
+ src: () => props.src,
36
+ alt: () => props.alt,
37
+ context: () => props.context ?? 'content',
38
+ sizes: () => props.sizes,
39
+ original: () => props.original,
40
+ })
41
+ </script>
42
+
43
+ <template>
44
+ <picture v-if="image.hasVariants && !original">
45
+ <source
46
+ v-for="source in image.sources"
47
+ :key="source.type"
48
+ :srcset="source.srcset"
49
+ :type="source.type"
50
+ :sizes="source.sizes"
51
+ />
52
+ <img v-bind="image.imgProps" :class="props.class" />
53
+ </picture>
54
+ <img v-else v-bind="image.imgProps" :class="props.class" />
55
+ </template>
@@ -1,10 +1,10 @@
1
- export { useTextContent } from './useTextContent'
2
- export { useSEO, createSiteSEO } from './useSEO'
3
- export { useReleaseNotes } from './useReleaseNotes'
4
- export { useSiteVersion } from './useSiteVersion'
5
- export {
6
- useReviewContent,
7
- type ReviewItem,
8
- type UseReviewContentConfig,
9
- type UseReviewContentReturn,
10
- } from './useReviewContent'
1
+ export { useTextContent } from './useTextContent'
2
+ export { useSEO, createSiteSEO } from './useSEO'
3
+ export { useReleaseNotes } from './useReleaseNotes'
4
+ export { useSiteVersion } from './useSiteVersion'
5
+ export {
6
+ useReviewContent,
7
+ type ReviewItem,
8
+ type UseReviewContentConfig,
9
+ type UseReviewContentReturn,
10
+ } from './useReviewContent'
@@ -1,158 +1,158 @@
1
- /**
2
- * useMediaCarousel Composable
3
- *
4
- * Extracts media carousel items from text content keys following the pattern:
5
- * `{prefix}.{N}.url`, `{prefix}.{N}.type`, `{prefix}.{N}.alt`
6
- *
7
- * @example
8
- * ```vue
9
- * <script setup lang="ts">
10
- * import { useTextContent, useMediaCarousel } from '@duffcloudservices/cms'
11
- *
12
- * const { t } = useTextContent({
13
- * pageSlug: 'bio-mackenzie',
14
- * defaults: {
15
- * 'hero.media-carousel.0.url': '/images/staff/mackenzie.webp',
16
- * 'hero.media-carousel.0.type': 'image',
17
- * 'hero.media-carousel.0.alt': 'Mackenzie Kowalick',
18
- * 'hero.media-carousel.1.url': '/videos/intro.mp4',
19
- * 'hero.media-carousel.1.type': 'video',
20
- * 'hero.media-carousel.1.alt': 'Introduction video',
21
- * }
22
- * })
23
- *
24
- * const { items } = useMediaCarousel({
25
- * prefix: 'hero.media-carousel',
26
- * t,
27
- * defaults: [
28
- * { url: '/images/staff/mackenzie.webp', type: 'image', alt: 'Mackenzie Kowalick' }
29
- * ]
30
- * })
31
- * </script>
32
- *
33
- * <template>
34
- * <MediaCarousel :items="items" />
35
- * </template>
36
- * ```
37
- */
38
-
39
- import { computed, type ComputedRef } from 'vue'
40
- import { isCdnAssetUrl } from '@duffcloudservices/cms-core'
41
-
42
- /**
43
- * Media carousel item representing an image, video, or embed
44
- */
45
- export interface MediaCarouselItem {
46
- /** URL to the image, video file, or embed URL */
47
- url: string
48
- /** Type of media: 'image', 'video' (direct file), 'youtube', or 'instagram' */
49
- type: 'image' | 'video' | 'youtube' | 'instagram'
50
- /** Accessibility alt text */
51
- alt?: string
52
- /**
53
- * Whether this image has responsive CDN variants available.
54
- * Automatically set to `true` when the URL matches the DCS CDN asset pattern.
55
- * Components rendering the carousel should use `<ResponsiveImage>` when this is `true`.
56
- */
57
- responsive?: boolean
58
- }
59
-
60
- /**
61
- * Configuration for useMediaCarousel composable
62
- */
63
- export interface UseMediaCarouselConfig {
64
- /** Key prefix for carousel items (e.g., 'hero.media-carousel') */
65
- prefix: string
66
- /** The t() function from useTextContent */
67
- t: (key: string, fallback?: string) => string
68
- /** Default items to use if no content keys are found */
69
- defaults?: MediaCarouselItem[]
70
- /** Maximum number of items to look for (default: 10) */
71
- maxItems?: number
72
- }
73
-
74
- /**
75
- * Return type for useMediaCarousel composable
76
- */
77
- export interface UseMediaCarouselReturn {
78
- /** Computed array of media carousel items */
79
- items: ComputedRef<MediaCarouselItem[]>
80
- /** Whether any items were found from content keys */
81
- hasItems: ComputedRef<boolean>
82
- /** Number of items in the carousel */
83
- count: ComputedRef<number>
84
- }
85
-
86
- /**
87
- * Extract media carousel items from text content keys.
88
- *
89
- * Looks for keys in the format:
90
- * - `{prefix}.{N}.url` - Required URL for the media
91
- * - `{prefix}.{N}.type` - Type: 'image' or 'video' (defaults to 'image')
92
- * - `{prefix}.{N}.alt` - Alt text for accessibility
93
- *
94
- * Items are sorted by index (0, 1, etc.) and only included if they have a valid URL.
95
- *
96
- * @param config - Configuration object
97
- * @returns Media carousel helpers and state
98
- */
99
- export function useMediaCarousel(config: UseMediaCarouselConfig): UseMediaCarouselReturn {
100
- const {
101
- prefix,
102
- t,
103
- defaults = [],
104
- maxItems = 10,
105
- } = config
106
-
107
- const items = computed<MediaCarouselItem[]>(() => {
108
- const result: MediaCarouselItem[] = []
109
-
110
- // Look for items from 0 to maxItems
111
- for (let i = 0; i < maxItems; i++) {
112
- const urlKey = `${prefix}.${i}.url`
113
- const typeKey = `${prefix}.${i}.type`
114
- const altKey = `${prefix}.${i}.alt`
115
-
116
- // Use a sentinel value to detect if the key exists
117
- const url = t(urlKey, '')
118
-
119
- // Skip if no URL (key doesn't exist or is empty)
120
- if (!url || url === urlKey) {
121
- continue
122
- }
123
-
124
- const typeValue = t(typeKey, 'image')
125
- // Parse type value - support image, video, youtube, instagram
126
- let type: 'image' | 'video' | 'youtube' | 'instagram' = 'image'
127
- if (typeValue === 'video') type = 'video'
128
- else if (typeValue === 'youtube') type = 'youtube'
129
- else if (typeValue === 'instagram') type = 'instagram'
130
- const alt = t(altKey, '')
131
-
132
- result.push({
133
- url,
134
- type,
135
- alt: alt && alt !== altKey ? alt : undefined,
136
- // Flag CDN-hosted images as responsive so carousel components
137
- // can render them with <ResponsiveImage> automatically
138
- responsive: type === 'image' && isCdnAssetUrl(url),
139
- })
140
- }
141
-
142
- // If no items found from content keys, use defaults
143
- if (result.length === 0) {
144
- return defaults
145
- }
146
-
147
- return result
148
- })
149
-
150
- const hasItems = computed(() => items.value.length > 0)
151
- const count = computed(() => items.value.length)
152
-
153
- return {
154
- items,
155
- hasItems,
156
- count,
157
- }
158
- }
1
+ /**
2
+ * useMediaCarousel Composable
3
+ *
4
+ * Extracts media carousel items from text content keys following the pattern:
5
+ * `{prefix}.{N}.url`, `{prefix}.{N}.type`, `{prefix}.{N}.alt`
6
+ *
7
+ * @example
8
+ * ```vue
9
+ * <script setup lang="ts">
10
+ * import { useTextContent, useMediaCarousel } from '@duffcloudservices/cms'
11
+ *
12
+ * const { t } = useTextContent({
13
+ * pageSlug: 'bio-mackenzie',
14
+ * defaults: {
15
+ * 'hero.media-carousel.0.url': '/images/staff/mackenzie.webp',
16
+ * 'hero.media-carousel.0.type': 'image',
17
+ * 'hero.media-carousel.0.alt': 'Mackenzie Kowalick',
18
+ * 'hero.media-carousel.1.url': '/videos/intro.mp4',
19
+ * 'hero.media-carousel.1.type': 'video',
20
+ * 'hero.media-carousel.1.alt': 'Introduction video',
21
+ * }
22
+ * })
23
+ *
24
+ * const { items } = useMediaCarousel({
25
+ * prefix: 'hero.media-carousel',
26
+ * t,
27
+ * defaults: [
28
+ * { url: '/images/staff/mackenzie.webp', type: 'image', alt: 'Mackenzie Kowalick' }
29
+ * ]
30
+ * })
31
+ * </script>
32
+ *
33
+ * <template>
34
+ * <MediaCarousel :items="items" />
35
+ * </template>
36
+ * ```
37
+ */
38
+
39
+ import { computed, type ComputedRef } from 'vue'
40
+ import { isCdnAssetUrl } from '@duffcloudservices/cms-core'
41
+
42
+ /**
43
+ * Media carousel item representing an image, video, or embed
44
+ */
45
+ export interface MediaCarouselItem {
46
+ /** URL to the image, video file, or embed URL */
47
+ url: string
48
+ /** Type of media: 'image', 'video' (direct file), 'youtube', or 'instagram' */
49
+ type: 'image' | 'video' | 'youtube' | 'instagram'
50
+ /** Accessibility alt text */
51
+ alt?: string
52
+ /**
53
+ * Whether this image has responsive CDN variants available.
54
+ * Automatically set to `true` when the URL matches the DCS CDN asset pattern.
55
+ * Components rendering the carousel should use `<ResponsiveImage>` when this is `true`.
56
+ */
57
+ responsive?: boolean
58
+ }
59
+
60
+ /**
61
+ * Configuration for useMediaCarousel composable
62
+ */
63
+ export interface UseMediaCarouselConfig {
64
+ /** Key prefix for carousel items (e.g., 'hero.media-carousel') */
65
+ prefix: string
66
+ /** The t() function from useTextContent */
67
+ t: (key: string, fallback?: string) => string
68
+ /** Default items to use if no content keys are found */
69
+ defaults?: MediaCarouselItem[]
70
+ /** Maximum number of items to look for (default: 10) */
71
+ maxItems?: number
72
+ }
73
+
74
+ /**
75
+ * Return type for useMediaCarousel composable
76
+ */
77
+ export interface UseMediaCarouselReturn {
78
+ /** Computed array of media carousel items */
79
+ items: ComputedRef<MediaCarouselItem[]>
80
+ /** Whether any items were found from content keys */
81
+ hasItems: ComputedRef<boolean>
82
+ /** Number of items in the carousel */
83
+ count: ComputedRef<number>
84
+ }
85
+
86
+ /**
87
+ * Extract media carousel items from text content keys.
88
+ *
89
+ * Looks for keys in the format:
90
+ * - `{prefix}.{N}.url` - Required URL for the media
91
+ * - `{prefix}.{N}.type` - Type: 'image' or 'video' (defaults to 'image')
92
+ * - `{prefix}.{N}.alt` - Alt text for accessibility
93
+ *
94
+ * Items are sorted by index (0, 1, etc.) and only included if they have a valid URL.
95
+ *
96
+ * @param config - Configuration object
97
+ * @returns Media carousel helpers and state
98
+ */
99
+ export function useMediaCarousel(config: UseMediaCarouselConfig): UseMediaCarouselReturn {
100
+ const {
101
+ prefix,
102
+ t,
103
+ defaults = [],
104
+ maxItems = 10,
105
+ } = config
106
+
107
+ const items = computed<MediaCarouselItem[]>(() => {
108
+ const result: MediaCarouselItem[] = []
109
+
110
+ // Look for items from 0 to maxItems
111
+ for (let i = 0; i < maxItems; i++) {
112
+ const urlKey = `${prefix}.${i}.url`
113
+ const typeKey = `${prefix}.${i}.type`
114
+ const altKey = `${prefix}.${i}.alt`
115
+
116
+ // Use a sentinel value to detect if the key exists
117
+ const url = t(urlKey, '')
118
+
119
+ // Skip if no URL (key doesn't exist or is empty)
120
+ if (!url || url === urlKey) {
121
+ continue
122
+ }
123
+
124
+ const typeValue = t(typeKey, 'image')
125
+ // Parse type value - support image, video, youtube, instagram
126
+ let type: 'image' | 'video' | 'youtube' | 'instagram' = 'image'
127
+ if (typeValue === 'video') type = 'video'
128
+ else if (typeValue === 'youtube') type = 'youtube'
129
+ else if (typeValue === 'instagram') type = 'instagram'
130
+ const alt = t(altKey, '')
131
+
132
+ result.push({
133
+ url,
134
+ type,
135
+ alt: alt && alt !== altKey ? alt : undefined,
136
+ // Flag CDN-hosted images as responsive so carousel components
137
+ // can render them with <ResponsiveImage> automatically
138
+ responsive: type === 'image' && isCdnAssetUrl(url),
139
+ })
140
+ }
141
+
142
+ // If no items found from content keys, use defaults
143
+ if (result.length === 0) {
144
+ return defaults
145
+ }
146
+
147
+ return result
148
+ })
149
+
150
+ const hasItems = computed(() => items.value.length > 0)
151
+ const count = computed(() => items.value.length)
152
+
153
+ return {
154
+ items,
155
+ hasItems,
156
+ count,
157
+ }
158
+ }