@wakastellar/ui 2.1.2 → 2.3.2
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.
- package/dist/blocks/apm-overview/index.d.ts +58 -0
- package/dist/blocks/cicd-builder/index.d.ts +47 -0
- package/dist/blocks/cloud-cost-dashboard/index.d.ts +49 -0
- package/dist/blocks/container-orchestrator/index.d.ts +63 -0
- package/dist/blocks/database-admin/index.d.ts +84 -0
- package/dist/blocks/gitops-sync-status/index.d.ts +45 -0
- package/dist/blocks/incident-manager/index.d.ts +44 -0
- package/dist/blocks/index.d.ts +10 -0
- package/dist/blocks/infrastructure-map/index.d.ts +32 -0
- package/dist/blocks/on-call-schedule/index.d.ts +43 -0
- package/dist/blocks/release-notes/index.d.ts +49 -0
- package/dist/components/index.d.ts +34 -0
- package/dist/components/waka-ad-banner/index.d.ts +36 -0
- package/dist/components/waka-ad-fallback/index.d.ts +33 -0
- package/dist/components/waka-ad-inline/index.d.ts +15 -0
- package/dist/components/waka-ad-interstitial/index.d.ts +26 -0
- package/dist/components/waka-ad-placeholder/index.d.ts +17 -0
- package/dist/components/waka-ad-provider/index.d.ts +103 -0
- package/dist/components/waka-ad-sidebar/index.d.ts +18 -0
- package/dist/components/waka-ad-sticky-footer/index.d.ts +17 -0
- package/dist/components/waka-alert-panel/index.d.ts +45 -0
- package/dist/components/waka-artifact-list/index.d.ts +32 -0
- package/dist/components/waka-build-matrix/index.d.ts +36 -0
- package/dist/components/waka-config-comparator/index.d.ts +37 -0
- package/dist/components/waka-container-list/index.d.ts +51 -0
- package/dist/components/waka-content-recommendation/index.d.ts +23 -0
- package/dist/components/waka-database-card/index.d.ts +46 -0
- package/dist/components/waka-dependency-tree/index.d.ts +38 -0
- package/dist/components/waka-env-var-editor/index.d.ts +30 -0
- package/dist/components/waka-feature-flag-row/index.d.ts +45 -0
- package/dist/components/waka-kubernetes-overview/index.d.ts +98 -0
- package/dist/components/waka-log-viewer/index.d.ts +38 -0
- package/dist/components/waka-migration-list/index.d.ts +36 -0
- package/dist/components/waka-outstream-video/index.d.ts +24 -0
- package/dist/components/waka-pod-card/index.d.ts +73 -0
- package/dist/components/waka-query-explain/index.d.ts +48 -0
- package/dist/components/waka-secret-card/index.d.ts +43 -0
- package/dist/components/waka-security-scan-result/index.d.ts +45 -0
- package/dist/components/waka-service-graph/index.d.ts +44 -0
- package/dist/components/waka-sponsored-badge/index.d.ts +20 -0
- package/dist/components/waka-sponsored-card/index.d.ts +25 -0
- package/dist/components/waka-sponsored-feed/index.d.ts +31 -0
- package/dist/components/waka-test-report/index.d.ts +60 -0
- package/dist/components/waka-trace-viewer/index.d.ts +36 -0
- package/dist/components/waka-video-ad/index.d.ts +32 -0
- package/dist/components/waka-video-overlay/index.d.ts +26 -0
- package/dist/index.cjs.js +251 -200
- package/dist/index.d.ts +1 -0
- package/dist/index.es.js +47315 -35823
- package/dist/utils/security.d.ts +96 -0
- package/package.json +4 -4
- package/src/blocks/apm-overview/index.tsx +672 -0
- package/src/blocks/cicd-builder/index.tsx +738 -0
- package/src/blocks/cloud-cost-dashboard/index.tsx +597 -0
- package/src/blocks/container-orchestrator/index.tsx +729 -0
- package/src/blocks/database-admin/index.tsx +679 -0
- package/src/blocks/gitops-sync-status/index.tsx +557 -0
- package/src/blocks/incident-manager/index.tsx +586 -0
- package/src/blocks/index.ts +119 -0
- package/src/blocks/infrastructure-map/index.tsx +638 -0
- package/src/blocks/on-call-schedule/index.tsx +615 -0
- package/src/blocks/release-notes/index.tsx +643 -0
- package/src/blocks/sidebar/index.tsx +6 -6
- package/src/components/DataTable/templates/index.tsx +3 -2
- package/src/components/index.ts +283 -0
- package/src/components/waka-3d-pie-chart/index.tsx +11 -11
- package/src/components/waka-achievement-unlock/index.tsx +16 -16
- package/src/components/waka-ad-banner/index.tsx +275 -0
- package/src/components/waka-ad-fallback/index.tsx +181 -0
- package/src/components/waka-ad-inline/index.tsx +103 -0
- package/src/components/waka-ad-interstitial/index.tsx +278 -0
- package/src/components/waka-ad-placeholder/index.tsx +84 -0
- package/src/components/waka-ad-provider/index.tsx +329 -0
- package/src/components/waka-ad-sidebar/index.tsx +113 -0
- package/src/components/waka-ad-sticky-footer/index.tsx +125 -0
- package/src/components/waka-alert-panel/index.tsx +493 -0
- package/src/components/waka-artifact-list/index.tsx +416 -0
- package/src/components/waka-badge-showcase/index.tsx +12 -11
- package/src/components/waka-build-matrix/index.tsx +396 -0
- package/src/components/waka-command-bar/index.tsx +2 -1
- package/src/components/waka-config-comparator/index.tsx +416 -0
- package/src/components/waka-container-list/index.tsx +475 -0
- package/src/components/waka-content-recommendation/index.tsx +294 -0
- package/src/components/waka-cost-breakdown/index.tsx +10 -10
- package/src/components/waka-database-card/index.tsx +473 -0
- package/src/components/waka-dependency-tree/index.tsx +542 -0
- package/src/components/waka-env-var-editor/index.tsx +417 -0
- package/src/components/waka-feature-flag-row/index.tsx +386 -0
- package/src/components/waka-funnel-chart/index.tsx +8 -8
- package/src/components/waka-health-pulse/index.tsx +6 -6
- package/src/components/waka-kubernetes-overview/index.tsx +536 -0
- package/src/components/waka-leaderboard/index.tsx +9 -9
- package/src/components/waka-log-viewer/index.tsx +386 -0
- package/src/components/waka-loot-box/index.tsx +20 -20
- package/src/components/waka-migration-list/index.tsx +487 -0
- package/src/components/waka-outstream-video/index.tsx +240 -0
- package/src/components/waka-player-card/index.tsx +5 -5
- package/src/components/waka-pod-card/index.tsx +528 -0
- package/src/components/waka-query-explain/index.tsx +657 -0
- package/src/components/waka-quota-bar/index.tsx +4 -4
- package/src/components/waka-radar-score/index.tsx +10 -10
- package/src/components/waka-scratch-card/index.tsx +5 -4
- package/src/components/waka-secret-card/index.tsx +371 -0
- package/src/components/waka-security-scan-result/index.tsx +473 -0
- package/src/components/waka-server-rack/index.tsx +28 -27
- package/src/components/waka-service-graph/index.tsx +445 -0
- package/src/components/waka-sponsored-badge/index.tsx +97 -0
- package/src/components/waka-sponsored-card/index.tsx +275 -0
- package/src/components/waka-sponsored-feed/index.tsx +127 -0
- package/src/components/waka-spotlight/index.tsx +2 -1
- package/src/components/waka-success-explosion/index.tsx +4 -4
- package/src/components/waka-test-report/index.tsx +469 -0
- package/src/components/waka-trace-viewer/index.tsx +490 -0
- package/src/components/waka-video-ad/index.tsx +406 -0
- package/src/components/waka-video-overlay/index.tsx +257 -0
- package/src/components/waka-xp-bar/index.tsx +13 -13
- package/src/styles/base.css +16 -0
- package/src/styles/tailwind.preset.js +12 -0
- package/src/styles/themes/forest.css +16 -0
- package/src/styles/themes/monochrome.css +16 -0
- package/src/styles/themes/perpetuity.css +16 -0
- package/src/styles/themes/sunset.css +16 -0
- package/src/styles/themes/twilight.css +16 -0
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { useRef, useEffect, useState, useCallback } from "react"
|
|
5
|
+
import { cn } from "../../utils/cn"
|
|
6
|
+
import { useAdContext, useAdVisibility, type CustomAd } from "../waka-ad-provider"
|
|
7
|
+
import { WakaSponsoredBadge } from "../waka-sponsored-badge"
|
|
8
|
+
import { ExternalLink } from "lucide-react"
|
|
9
|
+
|
|
10
|
+
export type SponsoredCardVariant = "article" | "product" | "compact" | "horizontal"
|
|
11
|
+
|
|
12
|
+
export interface WakaSponsoredCardProps {
|
|
13
|
+
/** Unique slot ID */
|
|
14
|
+
slotId: string
|
|
15
|
+
/** Card variant */
|
|
16
|
+
variant?: SponsoredCardVariant
|
|
17
|
+
/** GPT ad unit path */
|
|
18
|
+
adUnitPath?: string
|
|
19
|
+
/** Show image */
|
|
20
|
+
showImage?: boolean
|
|
21
|
+
/** Show description */
|
|
22
|
+
showDescription?: boolean
|
|
23
|
+
/** Show CTA button */
|
|
24
|
+
showCta?: boolean
|
|
25
|
+
/** Image aspect ratio */
|
|
26
|
+
aspectRatio?: "video" | "square" | "portrait"
|
|
27
|
+
/** Custom class name */
|
|
28
|
+
className?: string
|
|
29
|
+
/** Callback when clicked */
|
|
30
|
+
onClick?: () => void
|
|
31
|
+
/** Callback when loaded */
|
|
32
|
+
onLoad?: () => void
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function WakaSponsoredCard({
|
|
36
|
+
slotId,
|
|
37
|
+
variant = "article",
|
|
38
|
+
adUnitPath,
|
|
39
|
+
showImage = true,
|
|
40
|
+
showDescription = true,
|
|
41
|
+
showCta = true,
|
|
42
|
+
aspectRatio = "video",
|
|
43
|
+
className,
|
|
44
|
+
onClick,
|
|
45
|
+
onLoad,
|
|
46
|
+
}: WakaSponsoredCardProps) {
|
|
47
|
+
const containerRef = useRef<HTMLDivElement>(null)
|
|
48
|
+
const { config, isReady, hasConsent, getCustomAd, trackEvent } = useAdContext()
|
|
49
|
+
const { isVisible } = useAdVisibility(containerRef)
|
|
50
|
+
|
|
51
|
+
const [status, setStatus] = useState<"loading" | "loaded" | "error">("loading")
|
|
52
|
+
const [ad, setAd] = useState<CustomAd | null>(null)
|
|
53
|
+
|
|
54
|
+
// Load ad
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
if (!isReady || !isVisible || hasConsent === false) return
|
|
57
|
+
|
|
58
|
+
const loadAd = async () => {
|
|
59
|
+
try {
|
|
60
|
+
const customAd = await getCustomAd(slotId)
|
|
61
|
+
if (customAd) {
|
|
62
|
+
setAd(customAd)
|
|
63
|
+
setStatus("loaded")
|
|
64
|
+
trackEvent({ type: "loaded", slotId, timestamp: new Date() })
|
|
65
|
+
trackEvent({ type: "impression", slotId, timestamp: new Date() })
|
|
66
|
+
onLoad?.()
|
|
67
|
+
|
|
68
|
+
// Fire tracking
|
|
69
|
+
if (customAd.impressionUrl) {
|
|
70
|
+
fetch(customAd.impressionUrl, { mode: "no-cors" }).catch(() => {})
|
|
71
|
+
}
|
|
72
|
+
} else {
|
|
73
|
+
setStatus("error")
|
|
74
|
+
}
|
|
75
|
+
} catch {
|
|
76
|
+
setStatus("error")
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
loadAd()
|
|
81
|
+
}, [isReady, isVisible, hasConsent, slotId, getCustomAd, trackEvent, onLoad])
|
|
82
|
+
|
|
83
|
+
const handleClick = useCallback(() => {
|
|
84
|
+
if (!ad) return
|
|
85
|
+
|
|
86
|
+
trackEvent({ type: "click", slotId, timestamp: new Date() })
|
|
87
|
+
onClick?.()
|
|
88
|
+
|
|
89
|
+
if (ad.clickUrl) {
|
|
90
|
+
fetch(ad.clickUrl, { mode: "no-cors" }).catch(() => {})
|
|
91
|
+
}
|
|
92
|
+
if (ad.targetUrl) {
|
|
93
|
+
window.open(ad.targetUrl, "_blank", "noopener,noreferrer")
|
|
94
|
+
}
|
|
95
|
+
}, [ad, slotId, trackEvent, onClick])
|
|
96
|
+
|
|
97
|
+
const aspectRatioClasses = {
|
|
98
|
+
video: "aspect-video",
|
|
99
|
+
square: "aspect-square",
|
|
100
|
+
portrait: "aspect-[3/4]",
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (status === "error" || !ad) {
|
|
104
|
+
return null // Don't show anything if no ad
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (status === "loading") {
|
|
108
|
+
return (
|
|
109
|
+
<div
|
|
110
|
+
ref={containerRef}
|
|
111
|
+
className={cn(
|
|
112
|
+
"rounded-lg border bg-muted animate-pulse",
|
|
113
|
+
variant === "horizontal" ? "h-32" : "h-64",
|
|
114
|
+
className
|
|
115
|
+
)}
|
|
116
|
+
/>
|
|
117
|
+
)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Horizontal variant
|
|
121
|
+
if (variant === "horizontal") {
|
|
122
|
+
return (
|
|
123
|
+
<div ref={containerRef} className={cn("group", className)}>
|
|
124
|
+
<button
|
|
125
|
+
onClick={handleClick}
|
|
126
|
+
className="w-full flex gap-4 p-3 rounded-lg border bg-card hover:bg-accent/50 transition-colors text-left"
|
|
127
|
+
>
|
|
128
|
+
{showImage && ad.imageUrl && (
|
|
129
|
+
<div className="w-24 h-24 flex-shrink-0 rounded-md overflow-hidden bg-muted">
|
|
130
|
+
<img
|
|
131
|
+
src={ad.imageUrl}
|
|
132
|
+
alt=""
|
|
133
|
+
className="w-full h-full object-cover"
|
|
134
|
+
/>
|
|
135
|
+
</div>
|
|
136
|
+
)}
|
|
137
|
+
|
|
138
|
+
<div className="flex-1 min-w-0">
|
|
139
|
+
<div className="flex items-start justify-between gap-2 mb-1">
|
|
140
|
+
<h3 className="font-semibold text-sm line-clamp-2 group-hover:text-primary transition-colors">
|
|
141
|
+
{ad.title}
|
|
142
|
+
</h3>
|
|
143
|
+
<WakaSponsoredBadge sponsor={ad.sponsor} size="sm" showIcon={false} />
|
|
144
|
+
</div>
|
|
145
|
+
|
|
146
|
+
{showDescription && ad.description && (
|
|
147
|
+
<p className="text-xs text-muted-foreground line-clamp-2 mb-2">
|
|
148
|
+
{ad.description}
|
|
149
|
+
</p>
|
|
150
|
+
)}
|
|
151
|
+
|
|
152
|
+
{showCta && ad.cta && (
|
|
153
|
+
<span className="text-xs text-primary font-medium inline-flex items-center gap-1">
|
|
154
|
+
{ad.cta}
|
|
155
|
+
<ExternalLink className="h-3 w-3" />
|
|
156
|
+
</span>
|
|
157
|
+
)}
|
|
158
|
+
</div>
|
|
159
|
+
</button>
|
|
160
|
+
</div>
|
|
161
|
+
)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Compact variant
|
|
165
|
+
if (variant === "compact") {
|
|
166
|
+
return (
|
|
167
|
+
<div ref={containerRef} className={cn("group", className)}>
|
|
168
|
+
<button
|
|
169
|
+
onClick={handleClick}
|
|
170
|
+
className="w-full p-2 rounded-lg border bg-card hover:bg-accent/50 transition-colors text-left"
|
|
171
|
+
>
|
|
172
|
+
<div className="flex items-center gap-2 mb-1">
|
|
173
|
+
<WakaSponsoredBadge sponsor={ad.sponsor} size="sm" showIcon={false} />
|
|
174
|
+
</div>
|
|
175
|
+
<h3 className="font-medium text-sm line-clamp-2 group-hover:text-primary transition-colors">
|
|
176
|
+
{ad.title}
|
|
177
|
+
</h3>
|
|
178
|
+
</button>
|
|
179
|
+
</div>
|
|
180
|
+
)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Product variant
|
|
184
|
+
if (variant === "product") {
|
|
185
|
+
return (
|
|
186
|
+
<div ref={containerRef} className={cn("group", className)}>
|
|
187
|
+
<button
|
|
188
|
+
onClick={handleClick}
|
|
189
|
+
className="w-full rounded-lg border bg-card overflow-hidden hover:shadow-md transition-shadow text-left"
|
|
190
|
+
>
|
|
191
|
+
{showImage && ad.imageUrl && (
|
|
192
|
+
<div className={cn("relative bg-muted", aspectRatioClasses[aspectRatio])}>
|
|
193
|
+
<img
|
|
194
|
+
src={ad.imageUrl}
|
|
195
|
+
alt=""
|
|
196
|
+
className="absolute inset-0 w-full h-full object-cover"
|
|
197
|
+
/>
|
|
198
|
+
<div className="absolute top-2 left-2">
|
|
199
|
+
<WakaSponsoredBadge variant="subtle" size="sm" showIcon={false} />
|
|
200
|
+
</div>
|
|
201
|
+
</div>
|
|
202
|
+
)}
|
|
203
|
+
|
|
204
|
+
<div className="p-4">
|
|
205
|
+
<h3 className="font-semibold text-sm mb-1 group-hover:text-primary transition-colors line-clamp-2">
|
|
206
|
+
{ad.title}
|
|
207
|
+
</h3>
|
|
208
|
+
|
|
209
|
+
{showDescription && ad.description && (
|
|
210
|
+
<p className="text-xs text-muted-foreground line-clamp-2 mb-3">
|
|
211
|
+
{ad.description}
|
|
212
|
+
</p>
|
|
213
|
+
)}
|
|
214
|
+
|
|
215
|
+
{showCta && ad.cta && (
|
|
216
|
+
<span className="inline-flex items-center justify-center w-full px-4 py-2 bg-primary text-primary-foreground rounded-md text-sm font-medium hover:bg-primary/90 transition-colors">
|
|
217
|
+
{ad.cta}
|
|
218
|
+
</span>
|
|
219
|
+
)}
|
|
220
|
+
</div>
|
|
221
|
+
</button>
|
|
222
|
+
</div>
|
|
223
|
+
)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Article variant (default)
|
|
227
|
+
return (
|
|
228
|
+
<div ref={containerRef} className={cn("group", className)}>
|
|
229
|
+
<button
|
|
230
|
+
onClick={handleClick}
|
|
231
|
+
className="w-full rounded-lg border bg-card overflow-hidden hover:shadow-md transition-shadow text-left"
|
|
232
|
+
>
|
|
233
|
+
{showImage && ad.imageUrl && (
|
|
234
|
+
<div className={cn("relative bg-muted", aspectRatioClasses[aspectRatio])}>
|
|
235
|
+
<img
|
|
236
|
+
src={ad.imageUrl}
|
|
237
|
+
alt=""
|
|
238
|
+
className="absolute inset-0 w-full h-full object-cover group-hover:scale-105 transition-transform duration-300"
|
|
239
|
+
/>
|
|
240
|
+
<div className="absolute top-2 right-2">
|
|
241
|
+
<WakaSponsoredBadge variant="subtle" size="sm" />
|
|
242
|
+
</div>
|
|
243
|
+
</div>
|
|
244
|
+
)}
|
|
245
|
+
|
|
246
|
+
<div className="p-4">
|
|
247
|
+
{!showImage && (
|
|
248
|
+
<div className="mb-2">
|
|
249
|
+
<WakaSponsoredBadge sponsor={ad.sponsor} size="sm" />
|
|
250
|
+
</div>
|
|
251
|
+
)}
|
|
252
|
+
|
|
253
|
+
<h3 className="font-semibold mb-2 group-hover:text-primary transition-colors line-clamp-2">
|
|
254
|
+
{ad.title}
|
|
255
|
+
</h3>
|
|
256
|
+
|
|
257
|
+
{showDescription && ad.description && (
|
|
258
|
+
<p className="text-sm text-muted-foreground line-clamp-3 mb-3">
|
|
259
|
+
{ad.description}
|
|
260
|
+
</p>
|
|
261
|
+
)}
|
|
262
|
+
|
|
263
|
+
{showCta && ad.cta && (
|
|
264
|
+
<span className="text-sm text-primary font-medium inline-flex items-center gap-1">
|
|
265
|
+
{ad.cta}
|
|
266
|
+
<ExternalLink className="h-4 w-4" />
|
|
267
|
+
</span>
|
|
268
|
+
)}
|
|
269
|
+
</div>
|
|
270
|
+
</button>
|
|
271
|
+
</div>
|
|
272
|
+
)
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export default WakaSponsoredCard
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { cn } from "../../utils/cn"
|
|
5
|
+
import { WakaSponsoredCard, type SponsoredCardVariant } from "../waka-sponsored-card"
|
|
6
|
+
|
|
7
|
+
export interface WakaSponsoredFeedProps<T> {
|
|
8
|
+
/** Content items */
|
|
9
|
+
items: T[]
|
|
10
|
+
/** Render function for content items */
|
|
11
|
+
renderItem: (item: T, index: number) => React.ReactNode
|
|
12
|
+
/** Ad slot IDs to inject */
|
|
13
|
+
adSlots: Array<{
|
|
14
|
+
slotId: string
|
|
15
|
+
adUnitPath?: string
|
|
16
|
+
}>
|
|
17
|
+
/** Insert ad every N items */
|
|
18
|
+
adFrequency?: number
|
|
19
|
+
/** Starting position for first ad (0-indexed) */
|
|
20
|
+
adStartPosition?: number
|
|
21
|
+
/** Card variant for ads */
|
|
22
|
+
adVariant?: SponsoredCardVariant
|
|
23
|
+
/** Maximum number of ads to show */
|
|
24
|
+
maxAds?: number
|
|
25
|
+
/** Grid columns for items */
|
|
26
|
+
columns?: 1 | 2 | 3 | 4
|
|
27
|
+
/** Gap between items */
|
|
28
|
+
gap?: "sm" | "md" | "lg"
|
|
29
|
+
/** Custom class name */
|
|
30
|
+
className?: string
|
|
31
|
+
/** Key extractor for items */
|
|
32
|
+
keyExtractor?: (item: T, index: number) => string
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function WakaSponsoredFeed<T>({
|
|
36
|
+
items,
|
|
37
|
+
renderItem,
|
|
38
|
+
adSlots,
|
|
39
|
+
adFrequency = 5,
|
|
40
|
+
adStartPosition = 2,
|
|
41
|
+
adVariant = "article",
|
|
42
|
+
maxAds,
|
|
43
|
+
columns = 1,
|
|
44
|
+
gap = "md",
|
|
45
|
+
className,
|
|
46
|
+
keyExtractor,
|
|
47
|
+
}: WakaSponsoredFeedProps<T>) {
|
|
48
|
+
const gapClasses = {
|
|
49
|
+
sm: "gap-2",
|
|
50
|
+
md: "gap-4",
|
|
51
|
+
lg: "gap-6",
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const columnClasses = {
|
|
55
|
+
1: "grid-cols-1",
|
|
56
|
+
2: "grid-cols-1 sm:grid-cols-2",
|
|
57
|
+
3: "grid-cols-1 sm:grid-cols-2 lg:grid-cols-3",
|
|
58
|
+
4: "grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4",
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Calculate positions for ads
|
|
62
|
+
const getAdsForPosition = (position: number): typeof adSlots[number] | null => {
|
|
63
|
+
if (position < adStartPosition) return null
|
|
64
|
+
|
|
65
|
+
const adIndex = Math.floor((position - adStartPosition) / (adFrequency + 1))
|
|
66
|
+
const isAdPosition = (position - adStartPosition) % (adFrequency + 1) === 0
|
|
67
|
+
|
|
68
|
+
if (!isAdPosition) return null
|
|
69
|
+
if (maxAds !== undefined && adIndex >= maxAds) return null
|
|
70
|
+
if (adIndex >= adSlots.length) return null
|
|
71
|
+
|
|
72
|
+
return adSlots[adIndex]
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Build the feed with interspersed ads
|
|
76
|
+
const feedItems: Array<{ type: "content"; item: T; index: number } | { type: "ad"; slot: typeof adSlots[number] }> = []
|
|
77
|
+
let contentIndex = 0
|
|
78
|
+
let position = 0
|
|
79
|
+
|
|
80
|
+
while (contentIndex < items.length) {
|
|
81
|
+
const adSlot = getAdsForPosition(position)
|
|
82
|
+
|
|
83
|
+
if (adSlot) {
|
|
84
|
+
feedItems.push({ type: "ad", slot: adSlot })
|
|
85
|
+
} else {
|
|
86
|
+
feedItems.push({ type: "content", item: items[contentIndex], index: contentIndex })
|
|
87
|
+
contentIndex++
|
|
88
|
+
}
|
|
89
|
+
position++
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return (
|
|
93
|
+
<div
|
|
94
|
+
className={cn(
|
|
95
|
+
"grid",
|
|
96
|
+
columnClasses[columns],
|
|
97
|
+
gapClasses[gap],
|
|
98
|
+
className
|
|
99
|
+
)}
|
|
100
|
+
>
|
|
101
|
+
{feedItems.map((feedItem, idx) => {
|
|
102
|
+
if (feedItem.type === "ad") {
|
|
103
|
+
return (
|
|
104
|
+
<WakaSponsoredCard
|
|
105
|
+
key={`ad-${feedItem.slot.slotId}`}
|
|
106
|
+
slotId={feedItem.slot.slotId}
|
|
107
|
+
adUnitPath={feedItem.slot.adUnitPath}
|
|
108
|
+
variant={adVariant}
|
|
109
|
+
/>
|
|
110
|
+
)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const key = keyExtractor
|
|
114
|
+
? keyExtractor(feedItem.item, feedItem.index)
|
|
115
|
+
: `item-${feedItem.index}`
|
|
116
|
+
|
|
117
|
+
return (
|
|
118
|
+
<div key={key}>
|
|
119
|
+
{renderItem(feedItem.item, feedItem.index)}
|
|
120
|
+
</div>
|
|
121
|
+
)
|
|
122
|
+
})}
|
|
123
|
+
</div>
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export default WakaSponsoredFeed
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import * as React from "react"
|
|
4
4
|
import { cn } from "../../utils/cn"
|
|
5
|
+
import { safeNavigate } from "../../utils/security"
|
|
5
6
|
import { Search, X, Clock, ArrowRight, FileText, User, Settings, Folder, ExternalLink } from "lucide-react"
|
|
6
7
|
|
|
7
8
|
// ============================================================================
|
|
@@ -196,7 +197,7 @@ export function WakaSpotlight({
|
|
|
196
197
|
const handleSelect = (item: SpotlightResult) => {
|
|
197
198
|
item.action?.()
|
|
198
199
|
if (item.href) {
|
|
199
|
-
|
|
200
|
+
safeNavigate(item.href)
|
|
200
201
|
}
|
|
201
202
|
onOpenChange(false)
|
|
202
203
|
}
|
|
@@ -64,13 +64,13 @@ const sizeConfig = {
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
// ============================================================================
|
|
67
|
-
// Default colors
|
|
67
|
+
// Default colors (using CSS variables for theme support)
|
|
68
68
|
// ============================================================================
|
|
69
69
|
|
|
70
70
|
const defaultColors = {
|
|
71
|
-
primary: "
|
|
72
|
-
secondary: "
|
|
73
|
-
accent: "
|
|
71
|
+
primary: "hsl(var(--success))",
|
|
72
|
+
secondary: "hsl(var(--info))",
|
|
73
|
+
accent: "hsl(var(--warning))",
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
// ============================================================================
|