@lovett/ui 0.0.1
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/README.md +28 -0
- package/package.json +53 -0
- package/src/accordion.tsx +153 -0
- package/src/atoms.tsx +197 -0
- package/src/badge.tsx +91 -0
- package/src/brand-logo-tile.tsx +200 -0
- package/src/bulk-action-bar.tsx +104 -0
- package/src/button-group.tsx +46 -0
- package/src/button.tsx +93 -0
- package/src/calculator-shell-v2.tsx +380 -0
- package/src/calculator-shell.tsx +310 -0
- package/src/card.tsx +369 -0
- package/src/checkbox.tsx +114 -0
- package/src/chip-nav.tsx +347 -0
- package/src/choropleth.tsx +62 -0
- package/src/code-block.tsx +235 -0
- package/src/completion-ring.tsx +119 -0
- package/src/copy-field.tsx +169 -0
- package/src/data-grid/column-options-menu.tsx +127 -0
- package/src/data-grid/data-grid.tsx +391 -0
- package/src/data-grid/drawer-panel.tsx +146 -0
- package/src/data-grid/index.ts +29 -0
- package/src/data-grid/sortable-parts.tsx +215 -0
- package/src/data-grid/table-body.tsx +238 -0
- package/src/data-grid/table-elements.tsx +135 -0
- package/src/data-grid/table-header.tsx +95 -0
- package/src/data-grid/table-overlays.tsx +78 -0
- package/src/data-grid/table-parts.tsx +15 -0
- package/src/data-grid/table-summary-footer.tsx +101 -0
- package/src/data-grid/table-view.tsx +176 -0
- package/src/data-grid/types.ts +141 -0
- package/src/data-grid/use-grid-columns.ts +311 -0
- package/src/data-grid/use-grid-editing.ts +154 -0
- package/src/device-frame.tsx +274 -0
- package/src/dropdown-button.tsx +221 -0
- package/src/dropdown-menu.tsx +450 -0
- package/src/empty-state.tsx +85 -0
- package/src/file-upload/file-drop-zone.tsx +156 -0
- package/src/file-upload/file-preview-grid.tsx +39 -0
- package/src/file-upload/file-preview-item.tsx +175 -0
- package/src/file-upload/file-thumbnail.tsx +98 -0
- package/src/file-upload/file-upload-button.tsx +95 -0
- package/src/file-upload/index.ts +13 -0
- package/src/file-upload/use-file-upload.ts +371 -0
- package/src/filter-bar.tsx +125 -0
- package/src/filter-dropdown.tsx +289 -0
- package/src/floating-drawer.tsx +420 -0
- package/src/folder-card.tsx +188 -0
- package/src/folder-tree-picker.tsx +511 -0
- package/src/format.ts +117 -0
- package/src/frame-stack.tsx +73 -0
- package/src/hero-form-card.tsx +471 -0
- package/src/index.ts +372 -0
- package/src/input.tsx +130 -0
- package/src/kbd.tsx +41 -0
- package/src/lib/layout.ts +22 -0
- package/src/lib/utils.ts +6 -0
- package/src/markdown.tsx +214 -0
- package/src/menu-button.tsx +142 -0
- package/src/meta-previews/CreativeMedia.tsx +119 -0
- package/src/meta-previews/InstagramExplorePreview.tsx +340 -0
- package/src/meta-previews/InstagramFeedCarousel.tsx +245 -0
- package/src/meta-previews/InstagramFeedPreview.tsx +259 -0
- package/src/meta-previews/MetaFeedCarousel.tsx +228 -0
- package/src/meta-previews/MetaFeedPreview.tsx +294 -0
- package/src/meta-previews/MetaInstreamPreview.tsx +135 -0
- package/src/meta-previews/MetaMessengerPreview.tsx +795 -0
- package/src/meta-previews/MetaReelPreview.tsx +750 -0
- package/src/meta-previews/MetaRightColumnPreview.tsx +245 -0
- package/src/meta-previews/MetaSearchPreview.tsx +113 -0
- package/src/meta-previews/MetaStoryPreview.tsx +1132 -0
- package/src/meta-previews/ProfileAvatar.tsx +79 -0
- package/src/meta-previews/defaults.ts +27 -0
- package/src/meta-previews/index.ts +37 -0
- package/src/meta-previews/types.ts +219 -0
- package/src/metric-card.tsx +117 -0
- package/src/modal.tsx +131 -0
- package/src/option-tile.tsx +252 -0
- package/src/page-hero.tsx +78 -0
- package/src/page-shell.tsx +440 -0
- package/src/pagination.tsx +86 -0
- package/src/pill-button.tsx +103 -0
- package/src/profile-section.tsx +389 -0
- package/src/progress-bar.tsx +84 -0
- package/src/radio.tsx +172 -0
- package/src/range-slider.tsx +161 -0
- package/src/resizable.tsx +282 -0
- package/src/search-bar.tsx +81 -0
- package/src/segmented-pill.tsx +143 -0
- package/src/shell.tsx +464 -0
- package/src/slider.tsx +107 -0
- package/src/sortable-table.tsx +198 -0
- package/src/sortable.tsx +137 -0
- package/src/stat-row.tsx +132 -0
- package/src/stats-grid.tsx +38 -0
- package/src/step-loader.tsx +141 -0
- package/src/styles.css +562 -0
- package/src/tabs.tsx +143 -0
- package/src/tag-chip-input.tsx +394 -0
- package/src/textarea.tsx +39 -0
- package/src/toast.tsx +59 -0
- package/src/tokens.css +579 -0
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import { memo, useState } from 'react'
|
|
2
|
+
import type { ReactNode } from 'react'
|
|
3
|
+
import type { PreviewAdData } from './types'
|
|
4
|
+
import { CreativeMedia } from './CreativeMedia'
|
|
5
|
+
import { ProfileAvatar } from './ProfileAvatar'
|
|
6
|
+
import { withPreviewDefaults } from './defaults'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Facebook Feed — single image (sponsored). GREENFIELD rebuild.
|
|
10
|
+
*
|
|
11
|
+
* Not the Creative-Hub export extraction. Faithful to the real FB feed ad chrome
|
|
12
|
+
* but built cleanly: a 320px card with natural full-width flow (no fixed
|
|
13
|
+
* 300px / containerWidth-24 inner rows, no divider margins), Meta-exact hex
|
|
14
|
+
* (self-contained — no design tokens), a system font stack. Layout, top → bottom:
|
|
15
|
+
* header · primary text · creative · link card · divider · action bar
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
interface MetaFeedPreviewProps {
|
|
19
|
+
adData: PreviewAdData
|
|
20
|
+
format: '1:1' | '4:5' | '9:16'
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const WIDTH = 320
|
|
24
|
+
const FONT =
|
|
25
|
+
'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif'
|
|
26
|
+
|
|
27
|
+
const C = {
|
|
28
|
+
name: 'rgb(8, 8, 8)',
|
|
29
|
+
sub: 'rgb(101, 103, 107)',
|
|
30
|
+
body: 'rgb(0, 0, 0)',
|
|
31
|
+
seeMore: '#1877F2',
|
|
32
|
+
imageBg: '#F0F2F5',
|
|
33
|
+
imageEdge: 'rgba(0, 0, 0, 0.06)',
|
|
34
|
+
card: '#F7F8FA',
|
|
35
|
+
link: 'rgb(101, 103, 107)',
|
|
36
|
+
headline: 'rgb(28, 32, 40)',
|
|
37
|
+
desc: 'rgb(101, 103, 107)',
|
|
38
|
+
ctaBtnBg: 'rgb(228, 230, 235)',
|
|
39
|
+
ctaBtnInk: 'rgb(8, 8, 9)',
|
|
40
|
+
divider: 'rgb(219, 221, 224)',
|
|
41
|
+
action: 'rgb(97, 103, 112)',
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export const MetaFeedPreview = memo(function MetaFeedPreview({
|
|
45
|
+
adData: rawAdData,
|
|
46
|
+
format,
|
|
47
|
+
}: MetaFeedPreviewProps) {
|
|
48
|
+
const adData = withPreviewDefaults(rawAdData)
|
|
49
|
+
const [expanded, setExpanded] = useState(false)
|
|
50
|
+
|
|
51
|
+
const imageHeight = format === '4:5' ? 400 : WIDTH // 1:1 → 320, 4:5 → 400
|
|
52
|
+
const text = adData.primaryText ?? ''
|
|
53
|
+
const LIMIT = 125
|
|
54
|
+
const clipped = text.length > LIMIT && !expanded
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<div style={{ width: WIDTH, background: '#fff', fontFamily: FONT, color: C.body }}>
|
|
58
|
+
{/* Header */}
|
|
59
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '10px 12px' }}>
|
|
60
|
+
<div style={{ width: 40, height: 40, borderRadius: '50%', overflow: 'hidden', flexShrink: 0 }}>
|
|
61
|
+
<ProfileAvatar src={adData.profileImage} name={adData.brandName} fontSize={15} />
|
|
62
|
+
</div>
|
|
63
|
+
<div style={{ flex: 1, minWidth: 0 }}>
|
|
64
|
+
<div
|
|
65
|
+
style={{
|
|
66
|
+
fontSize: 14,
|
|
67
|
+
fontWeight: 600,
|
|
68
|
+
lineHeight: '18px',
|
|
69
|
+
color: C.name,
|
|
70
|
+
overflow: 'hidden',
|
|
71
|
+
textOverflow: 'ellipsis',
|
|
72
|
+
whiteSpace: 'nowrap',
|
|
73
|
+
}}
|
|
74
|
+
>
|
|
75
|
+
{adData.brandName}
|
|
76
|
+
</div>
|
|
77
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 4, marginTop: 1, color: C.sub }}>
|
|
78
|
+
<span style={{ fontSize: 12, lineHeight: '15px' }}>Sponsored</span>
|
|
79
|
+
<span style={{ fontSize: 12 }}>·</span>
|
|
80
|
+
<Globe />
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
<Dots />
|
|
84
|
+
</div>
|
|
85
|
+
|
|
86
|
+
{/* Primary text */}
|
|
87
|
+
{text && (
|
|
88
|
+
<div style={{ padding: '0 12px 10px', fontSize: 14, fontWeight: 400, lineHeight: '19px', color: C.body, whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}>
|
|
89
|
+
{clipped ? text.slice(0, LIMIT).trimEnd() : text}
|
|
90
|
+
{clipped && (
|
|
91
|
+
<>
|
|
92
|
+
{'… '}
|
|
93
|
+
<button
|
|
94
|
+
type="button"
|
|
95
|
+
onClick={() => setExpanded(true)}
|
|
96
|
+
style={{ border: 0, background: 'none', padding: 0, color: C.seeMore, fontWeight: 600, fontSize: 14, cursor: 'pointer', fontFamily: FONT }}
|
|
97
|
+
>
|
|
98
|
+
See more
|
|
99
|
+
</button>
|
|
100
|
+
</>
|
|
101
|
+
)}
|
|
102
|
+
</div>
|
|
103
|
+
)}
|
|
104
|
+
|
|
105
|
+
{/* Creative */}
|
|
106
|
+
<div
|
|
107
|
+
style={{
|
|
108
|
+
width: WIDTH,
|
|
109
|
+
height: imageHeight,
|
|
110
|
+
background: C.imageBg,
|
|
111
|
+
borderTop: `1px solid ${C.imageEdge}`,
|
|
112
|
+
borderBottom: `1px solid ${C.imageEdge}`,
|
|
113
|
+
overflow: 'hidden',
|
|
114
|
+
}}
|
|
115
|
+
>
|
|
116
|
+
{adData.creativeImage ? (
|
|
117
|
+
<CreativeMedia
|
|
118
|
+
src={adData.creativeImage}
|
|
119
|
+
mediaType={adData.creativeMediaType}
|
|
120
|
+
alt=""
|
|
121
|
+
style={{ width: WIDTH, height: imageHeight, objectFit: 'cover', display: 'block' }}
|
|
122
|
+
/>
|
|
123
|
+
) : (
|
|
124
|
+
<Placeholder format={format} />
|
|
125
|
+
)}
|
|
126
|
+
</div>
|
|
127
|
+
|
|
128
|
+
{/* Link card */}
|
|
129
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 12, padding: 12, background: C.card }}>
|
|
130
|
+
<div style={{ flex: 1, minWidth: 0 }}>
|
|
131
|
+
{adData.displayLink && (
|
|
132
|
+
<div
|
|
133
|
+
style={{
|
|
134
|
+
fontSize: 12,
|
|
135
|
+
lineHeight: '16px',
|
|
136
|
+
color: C.link,
|
|
137
|
+
textTransform: 'uppercase',
|
|
138
|
+
overflow: 'hidden',
|
|
139
|
+
textOverflow: 'ellipsis',
|
|
140
|
+
whiteSpace: 'nowrap',
|
|
141
|
+
}}
|
|
142
|
+
>
|
|
143
|
+
{adData.displayLink}
|
|
144
|
+
</div>
|
|
145
|
+
)}
|
|
146
|
+
{adData.headline && (
|
|
147
|
+
<div
|
|
148
|
+
style={{
|
|
149
|
+
fontSize: 15,
|
|
150
|
+
fontWeight: 700,
|
|
151
|
+
lineHeight: '19px',
|
|
152
|
+
color: C.headline,
|
|
153
|
+
marginTop: 1,
|
|
154
|
+
overflow: 'hidden',
|
|
155
|
+
textOverflow: 'ellipsis',
|
|
156
|
+
display: '-webkit-box',
|
|
157
|
+
WebkitLineClamp: 2,
|
|
158
|
+
WebkitBoxOrient: 'vertical',
|
|
159
|
+
}}
|
|
160
|
+
>
|
|
161
|
+
{adData.headline}
|
|
162
|
+
</div>
|
|
163
|
+
)}
|
|
164
|
+
{adData.description && (
|
|
165
|
+
<div
|
|
166
|
+
style={{
|
|
167
|
+
fontSize: 13,
|
|
168
|
+
lineHeight: '17px',
|
|
169
|
+
color: C.desc,
|
|
170
|
+
marginTop: 2,
|
|
171
|
+
overflow: 'hidden',
|
|
172
|
+
textOverflow: 'ellipsis',
|
|
173
|
+
whiteSpace: 'nowrap',
|
|
174
|
+
}}
|
|
175
|
+
>
|
|
176
|
+
{adData.description}
|
|
177
|
+
</div>
|
|
178
|
+
)}
|
|
179
|
+
</div>
|
|
180
|
+
<div
|
|
181
|
+
style={{
|
|
182
|
+
flexShrink: 0,
|
|
183
|
+
display: 'inline-flex',
|
|
184
|
+
alignItems: 'center',
|
|
185
|
+
height: 36,
|
|
186
|
+
padding: '0 14px',
|
|
187
|
+
borderRadius: 6,
|
|
188
|
+
background: C.ctaBtnBg,
|
|
189
|
+
fontSize: 14,
|
|
190
|
+
fontWeight: 600,
|
|
191
|
+
color: C.ctaBtnInk,
|
|
192
|
+
whiteSpace: 'nowrap',
|
|
193
|
+
cursor: 'pointer',
|
|
194
|
+
}}
|
|
195
|
+
>
|
|
196
|
+
{adData.callToAction || 'Learn more'}
|
|
197
|
+
</div>
|
|
198
|
+
</div>
|
|
199
|
+
|
|
200
|
+
{/* Divider */}
|
|
201
|
+
<div style={{ height: 1, background: C.divider }} />
|
|
202
|
+
|
|
203
|
+
{/* Action bar */}
|
|
204
|
+
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-around', padding: '6px 8px' }}>
|
|
205
|
+
<Action label="Like"><ThumbsUp /></Action>
|
|
206
|
+
<Action label="Comment"><Bubble /></Action>
|
|
207
|
+
<Action label="Share"><ShareArrow /></Action>
|
|
208
|
+
</div>
|
|
209
|
+
</div>
|
|
210
|
+
)
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
/* ------------------------------- icons / bits ------------------------------- */
|
|
214
|
+
|
|
215
|
+
const stroke = {
|
|
216
|
+
fill: 'none',
|
|
217
|
+
stroke: C.action,
|
|
218
|
+
strokeWidth: 2,
|
|
219
|
+
strokeLinecap: 'round' as const,
|
|
220
|
+
strokeLinejoin: 'round' as const,
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function Action({ label, children }: { label: string; children: ReactNode }) {
|
|
224
|
+
return (
|
|
225
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 6, padding: '4px 8px', cursor: 'pointer' }}>
|
|
226
|
+
{children}
|
|
227
|
+
<span style={{ fontSize: 13, fontWeight: 600, color: C.action }}>{label}</span>
|
|
228
|
+
</div>
|
|
229
|
+
)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function Dots() {
|
|
233
|
+
return (
|
|
234
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill={C.sub} style={{ display: 'block', flexShrink: 0 }}>
|
|
235
|
+
<circle cx="5" cy="12" r="1.6" />
|
|
236
|
+
<circle cx="12" cy="12" r="1.6" />
|
|
237
|
+
<circle cx="19" cy="12" r="1.6" />
|
|
238
|
+
</svg>
|
|
239
|
+
)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function Globe() {
|
|
243
|
+
return (
|
|
244
|
+
<svg width="12" height="12" viewBox="0 0 16 16" fill={C.sub} style={{ display: 'block' }}>
|
|
245
|
+
<path d="M8 0a8 8 0 1 0 8 8 8 8 0 0 0-8-8zm5.9 7H11a13 13 0 0 0-.9-4.4A6 6 0 0 1 13.9 7zM8 14c-.7 0-1.8-1.8-2-5h4c-.2 3.2-1.3 5-2 5zM6 7c.2-3.2 1.3-5 2-5s1.8 1.8 2 5zm-.1-4.4A13 13 0 0 0 5 7H2.1a6 6 0 0 1 3.8-4.4zM2.1 9H5a13 13 0 0 0 .9 4.4A6 6 0 0 1 2.1 9zm8 4.4a13 13 0 0 0 .9-4.4h2.9a6 6 0 0 1-3.8 4.4z" />
|
|
246
|
+
</svg>
|
|
247
|
+
)
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function ThumbsUp() {
|
|
251
|
+
return (
|
|
252
|
+
<svg width="18" height="18" viewBox="0 0 24 24" {...stroke} style={{ display: 'block' }}>
|
|
253
|
+
<path d="M14 9V5a3 3 0 0 0-3-3l-4 9v11h11.28a2 2 0 0 0 2-1.7l1.38-9a2 2 0 0 0-2-2.3zM7 22H4a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2h3" />
|
|
254
|
+
</svg>
|
|
255
|
+
)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function Bubble() {
|
|
259
|
+
return (
|
|
260
|
+
<svg width="18" height="18" viewBox="0 0 24 24" {...stroke} style={{ display: 'block' }}>
|
|
261
|
+
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" />
|
|
262
|
+
</svg>
|
|
263
|
+
)
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function ShareArrow() {
|
|
267
|
+
return (
|
|
268
|
+
<svg width="18" height="18" viewBox="0 0 24 24" {...stroke} style={{ display: 'block' }}>
|
|
269
|
+
<path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8" />
|
|
270
|
+
<polyline points="16 6 12 2 8 6" />
|
|
271
|
+
<line x1="12" y1="2" x2="12" y2="15" />
|
|
272
|
+
</svg>
|
|
273
|
+
)
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function Placeholder({ format }: { format: string }) {
|
|
277
|
+
return (
|
|
278
|
+
<div
|
|
279
|
+
style={{
|
|
280
|
+
width: '100%',
|
|
281
|
+
height: '100%',
|
|
282
|
+
display: 'flex',
|
|
283
|
+
flexDirection: 'column',
|
|
284
|
+
alignItems: 'center',
|
|
285
|
+
justifyContent: 'center',
|
|
286
|
+
gap: 8,
|
|
287
|
+
color: 'rgb(140, 140, 140)',
|
|
288
|
+
}}
|
|
289
|
+
>
|
|
290
|
+
<div style={{ width: 56, height: 56, borderRadius: 10, background: '#E4E6EB' }} />
|
|
291
|
+
<span style={{ fontSize: 13 }}>Ad creative · {format}</span>
|
|
292
|
+
</div>
|
|
293
|
+
)
|
|
294
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { ThumbsUp, MessageCircle, Share2, MoreHorizontal, X } from 'lucide-react';
|
|
3
|
+
import { CreativeMedia } from './CreativeMedia';
|
|
4
|
+
import type { PreviewAdData } from './types';
|
|
5
|
+
|
|
6
|
+
interface MetaInstreamPreviewProps {
|
|
7
|
+
adData: PreviewAdData;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const MetaInstreamPreview: React.FC<MetaInstreamPreviewProps> = ({
|
|
11
|
+
adData,
|
|
12
|
+
}) => {
|
|
13
|
+
// Truncate text helper
|
|
14
|
+
const truncateText = (text: string, limit: number) => {
|
|
15
|
+
if (!text || text.length <= limit) return text;
|
|
16
|
+
return text.substring(0, limit);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<div className="meta-preview w-full h-full">
|
|
21
|
+
<div
|
|
22
|
+
className="bg-white rounded-md overflow-hidden w-full h-full flex flex-col"
|
|
23
|
+
style={{}}
|
|
24
|
+
>
|
|
25
|
+
{/* Skeleton Header */}
|
|
26
|
+
<div className="p-3 flex items-center gap-2.5">
|
|
27
|
+
<div className="w-10 h-10 rounded-full bg-gray-200 animate-pulse flex-shrink-0" />
|
|
28
|
+
<div className="flex-1 min-w-0">
|
|
29
|
+
<div className="h-3 bg-gray-200 rounded w-32 mb-2 animate-pulse" />
|
|
30
|
+
<div className="h-2 bg-gray-200 rounded w-24 animate-pulse" />
|
|
31
|
+
</div>
|
|
32
|
+
<button className="w-8 h-8 flex items-center justify-center text-gray-400">
|
|
33
|
+
<MoreHorizontal className="w-5 h-5" />
|
|
34
|
+
</button>
|
|
35
|
+
</div>
|
|
36
|
+
|
|
37
|
+
{/* Main Video/Image Creative - Square 1:1 */}
|
|
38
|
+
<div className="relative w-full" style={{ paddingBottom: '100%' }}>
|
|
39
|
+
{adData.creativeImage ? (
|
|
40
|
+
<CreativeMedia
|
|
41
|
+
src={adData.creativeImage}
|
|
42
|
+
mediaType={adData.creativeMediaType}
|
|
43
|
+
alt="In-stream ad"
|
|
44
|
+
className="absolute inset-0 w-full h-full object-cover"
|
|
45
|
+
autoPlay
|
|
46
|
+
/>
|
|
47
|
+
) : (
|
|
48
|
+
<div className="absolute inset-0 bg-gray-100 flex items-center justify-center">
|
|
49
|
+
<div className="text-center text-gray-400">
|
|
50
|
+
<div className="w-16 h-16 bg-gray-200 rounded-lg mx-auto mb-2" />
|
|
51
|
+
<p className="text-sm">1:1 Video</p>
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
)}
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
{/* Thumbnail Section */}
|
|
58
|
+
<div className="p-3 border-t border-gray-200">
|
|
59
|
+
<div className="flex gap-3">
|
|
60
|
+
{/* Thumbnail */}
|
|
61
|
+
<div className="w-24 h-24 flex-shrink-0 rounded-lg overflow-hidden bg-gray-100">
|
|
62
|
+
{adData.creativeImage ? (
|
|
63
|
+
<CreativeMedia
|
|
64
|
+
src={adData.creativeImage}
|
|
65
|
+
mediaType={adData.creativeMediaType}
|
|
66
|
+
alt="Thumbnail"
|
|
67
|
+
className="w-full h-full object-cover"
|
|
68
|
+
showPlayOverlay={false}
|
|
69
|
+
/>
|
|
70
|
+
) : (
|
|
71
|
+
<div className="w-full h-full bg-gray-200" />
|
|
72
|
+
)}
|
|
73
|
+
</div>
|
|
74
|
+
|
|
75
|
+
{/* Ad Content */}
|
|
76
|
+
<div className="flex-1 min-w-0">
|
|
77
|
+
{/* Headline */}
|
|
78
|
+
<h3 className="font-semibold text-sm text-gray-900 mb-1 line-clamp-2">
|
|
79
|
+
{truncateText(adData.headline, 60)}...
|
|
80
|
+
</h3>
|
|
81
|
+
|
|
82
|
+
{/* Sponsored + Display Link */}
|
|
83
|
+
<div className="flex items-center gap-1 text-xs text-gray-500 mb-1">
|
|
84
|
+
<span>Sponsored</span>
|
|
85
|
+
<span>·</span>
|
|
86
|
+
<span className="truncate">{adData.displayLink}</span>
|
|
87
|
+
</div>
|
|
88
|
+
|
|
89
|
+
{/* Primary Text - truncated */}
|
|
90
|
+
<p className="text-xs text-gray-600 line-clamp-2">
|
|
91
|
+
{truncateText(adData.primaryText, 80)}...
|
|
92
|
+
</p>
|
|
93
|
+
</div>
|
|
94
|
+
|
|
95
|
+
{/* X and Ellipsis buttons */}
|
|
96
|
+
<div className="flex flex-col gap-1 flex-shrink-0">
|
|
97
|
+
<button className="w-6 h-6 flex items-center justify-center text-gray-400 hover:text-gray-600">
|
|
98
|
+
<X className="w-4 h-4" />
|
|
99
|
+
</button>
|
|
100
|
+
<button className="w-6 h-6 flex items-center justify-center text-gray-400 hover:text-gray-600">
|
|
101
|
+
<MoreHorizontal className="w-4 h-4" />
|
|
102
|
+
</button>
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
|
|
107
|
+
{/* Skeleton engagement section */}
|
|
108
|
+
<div className="px-3 py-2 border-t border-gray-200">
|
|
109
|
+
<div className="flex items-center gap-4">
|
|
110
|
+
<div className="h-2 bg-gray-200 rounded w-16 animate-pulse" />
|
|
111
|
+
<div className="h-2 bg-gray-200 rounded w-20 animate-pulse" />
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
114
|
+
|
|
115
|
+
{/* Action Buttons */}
|
|
116
|
+
<div className="px-3 py-2 border-t border-gray-200">
|
|
117
|
+
<div className="flex items-center justify-around">
|
|
118
|
+
<button className="flex items-center gap-2 px-4 py-2 text-gray-600 hover:bg-gray-50 rounded-md transition-colors">
|
|
119
|
+
<ThumbsUp className="w-5 h-5" />
|
|
120
|
+
<span className="text-sm font-medium">Like</span>
|
|
121
|
+
</button>
|
|
122
|
+
<button className="flex items-center gap-2 px-4 py-2 text-gray-600 hover:bg-gray-50 rounded-md transition-colors">
|
|
123
|
+
<MessageCircle className="w-5 h-5" />
|
|
124
|
+
<span className="text-sm font-medium">Comment</span>
|
|
125
|
+
</button>
|
|
126
|
+
<button className="flex items-center gap-2 px-4 py-2 text-gray-600 hover:bg-gray-50 rounded-md transition-colors">
|
|
127
|
+
<Share2 className="w-5 h-5" />
|
|
128
|
+
<span className="text-sm font-medium">Share</span>
|
|
129
|
+
</button>
|
|
130
|
+
</div>
|
|
131
|
+
</div>
|
|
132
|
+
</div>
|
|
133
|
+
</div>
|
|
134
|
+
);
|
|
135
|
+
};
|