@kern-di/trust-carousel 0.1.0
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/index.d.ts +21 -0
- package/dist/index.js +230 -0
- package/dist/index.js.map +1 -0
- package/package.json +63 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
|
|
3
|
+
interface GoogleReview {
|
|
4
|
+
author: string;
|
|
5
|
+
rating: number;
|
|
6
|
+
date: string;
|
|
7
|
+
text: string;
|
|
8
|
+
photoUrl?: string;
|
|
9
|
+
}
|
|
10
|
+
interface GoogleReviewsCarouselProps {
|
|
11
|
+
reviews: GoogleReview[];
|
|
12
|
+
businessName: string;
|
|
13
|
+
placeUrl: string;
|
|
14
|
+
accentColor?: string;
|
|
15
|
+
autoPlay?: boolean;
|
|
16
|
+
autoPlaySpeed?: number;
|
|
17
|
+
}
|
|
18
|
+
declare const SAMPLE_REVIEWS: GoogleReview[];
|
|
19
|
+
declare function GoogleReviewsCarousel({ reviews, businessName, placeUrl, autoPlay, autoPlaySpeed, }: GoogleReviewsCarouselProps): react_jsx_runtime.JSX.Element;
|
|
20
|
+
|
|
21
|
+
export { type GoogleReview, GoogleReviewsCarousel, type GoogleReviewsCarouselProps, SAMPLE_REVIEWS };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
// src/components/GoogleReviewsCarousel.tsx
|
|
2
|
+
import { useState, useEffect, useCallback } from "react";
|
|
3
|
+
import { ChevronLeft, ChevronRight, Star, ExternalLink } from "lucide-react";
|
|
4
|
+
|
|
5
|
+
// src/lib/cn.ts
|
|
6
|
+
import { clsx } from "clsx";
|
|
7
|
+
import { twMerge } from "tailwind-merge";
|
|
8
|
+
function cn(...inputs) {
|
|
9
|
+
return twMerge(clsx(inputs));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// src/components/GoogleReviewsCarousel.tsx
|
|
13
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
14
|
+
var SAMPLE_REVIEWS = [
|
|
15
|
+
{
|
|
16
|
+
author: "Sarah Mitchell",
|
|
17
|
+
rating: 5,
|
|
18
|
+
date: "2 weeks ago",
|
|
19
|
+
text: "Absolutely fantastic service! They fixed our burst pipe on a Sunday evening within an hour of calling. The plumber was professional, tidy, and explained everything clearly. Pricing was very fair \u2014 no hidden charges. Highly recommend to anyone needing reliable plumbing work."
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
author: "James Thornton",
|
|
23
|
+
rating: 5,
|
|
24
|
+
date: "1 month ago",
|
|
25
|
+
text: "Had a full rewire done on our 1930s semi. The team were punctual every single day, kept the house as clean as possible, and the finish is immaculate. They even helped us plan the socket layout for our new kitchen. Top-notch electricians."
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
author: "Maria Chen",
|
|
29
|
+
rating: 5,
|
|
30
|
+
date: "3 weeks ago",
|
|
31
|
+
text: "Called them for an emergency boiler repair in the middle of winter. They came out the same day and had the heating back on within two hours. Genuine lifesavers \u2014 we have young kids and couldn't have waited. Will use again for our annual service."
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
author: "David Okonkwo",
|
|
35
|
+
rating: 4,
|
|
36
|
+
date: "2 months ago",
|
|
37
|
+
text: "Great job installing our new bathroom. Tiling work is spotless and the underfloor heating is a dream. Only minor hiccup was a small delay waiting for parts, but they kept us updated throughout. Overall very happy with the result."
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
author: "Emma Richardson",
|
|
41
|
+
rating: 5,
|
|
42
|
+
date: "1 week ago",
|
|
43
|
+
text: "We've used this company three times now \u2014 blocked drain, leaking shower, and a full kitchen tap replacement. Every time the service has been prompt, friendly, and reasonably priced. It's so hard to find tradespeople you can trust, and these guys are the real deal."
|
|
44
|
+
}
|
|
45
|
+
];
|
|
46
|
+
var AVATAR_COLORS = [
|
|
47
|
+
"hsl(210, 60%, 50%)",
|
|
48
|
+
"hsl(340, 60%, 50%)",
|
|
49
|
+
"hsl(160, 55%, 42%)",
|
|
50
|
+
"hsl(30, 70%, 50%)",
|
|
51
|
+
"hsl(270, 50%, 55%)"
|
|
52
|
+
];
|
|
53
|
+
function getInitial(name) {
|
|
54
|
+
return name.charAt(0).toUpperCase();
|
|
55
|
+
}
|
|
56
|
+
function StarRating({ rating, size = 16 }) {
|
|
57
|
+
return /* @__PURE__ */ jsx("span", { className: "inline-flex gap-0.5", children: Array.from({ length: 5 }).map((_, i) => /* @__PURE__ */ jsx(
|
|
58
|
+
Star,
|
|
59
|
+
{
|
|
60
|
+
size,
|
|
61
|
+
className: i < rating ? "fill-amber-400 text-amber-400" : "text-muted-foreground/30"
|
|
62
|
+
},
|
|
63
|
+
i
|
|
64
|
+
)) });
|
|
65
|
+
}
|
|
66
|
+
function GoogleLogo({ className }) {
|
|
67
|
+
return /* @__PURE__ */ jsxs("svg", { className, viewBox: "0 0 272 92", xmlns: "http://www.w3.org/2000/svg", children: [
|
|
68
|
+
/* @__PURE__ */ jsx("path", { d: "M115.75 47.18c0 12.77-9.99 22.18-22.25 22.18s-22.25-9.41-22.25-22.18C71.25 34.32 81.24 25 93.5 25s22.25 9.32 22.25 22.18zm-9.74 0c0-7.98-5.79-13.44-12.51-13.44S80.99 39.2 80.99 47.18c0 7.9 5.79 13.44 12.51 13.44s12.51-5.55 12.51-13.44z", fill: "#EA4335" }),
|
|
69
|
+
/* @__PURE__ */ jsx("path", { d: "M163.75 47.18c0 12.77-9.99 22.18-22.25 22.18s-22.25-9.41-22.25-22.18c0-12.85 9.99-22.18 22.25-22.18s22.25 9.32 22.25 22.18zm-9.74 0c0-7.98-5.79-13.44-12.51-13.44s-12.51 5.46-12.51 13.44c0 7.9 5.79 13.44 12.51 13.44s12.51-5.55 12.51-13.44z", fill: "#FBBC05" }),
|
|
70
|
+
/* @__PURE__ */ jsx("path", { d: "M209.75 26.34v39.82c0 16.38-9.66 23.07-21.08 23.07-10.75 0-17.22-7.19-19.66-13.07l8.48-3.53c1.51 3.61 5.21 7.87 11.17 7.87 7.31 0 11.84-4.51 11.84-13v-3.19h-.34c-2.18 2.69-6.38 5.04-11.68 5.04-11.09 0-21.25-9.66-21.25-22.09 0-12.52 10.16-22.26 21.25-22.26 5.29 0 9.49 2.35 11.68 4.96h.34v-3.61h9.25zm-8.56 20.92c0-7.81-5.21-13.52-11.84-13.52-6.72 0-12.35 5.71-12.35 13.52 0 7.73 5.63 13.36 12.35 13.36 6.63 0 11.84-5.63 11.84-13.36z", fill: "#4285F4" }),
|
|
71
|
+
/* @__PURE__ */ jsx("path", { d: "M225 3v65h-9.5V3h9.5z", fill: "#34A853" }),
|
|
72
|
+
/* @__PURE__ */ jsx("path", { d: "M262.02 54.48l7.56 5.04c-2.44 3.61-8.32 9.83-18.48 9.83-12.6 0-22.01-9.74-22.01-22.18 0-13.19 9.49-22.18 20.92-22.18 11.51 0 17.14 9.16 18.98 14.11l1.01 2.52-29.65 12.28c2.27 4.45 5.8 6.72 10.75 6.72 4.96 0 8.4-2.44 10.92-6.14zm-23.27-7.98l19.82-8.23c-1.09-2.77-4.37-4.7-8.23-4.7-4.95 0-11.84 4.37-11.59 12.93z", fill: "#EA4335" }),
|
|
73
|
+
/* @__PURE__ */ jsx("path", { d: "M35.29 41.19V32H67c.31 1.64.47 3.58.47 5.68 0 7.06-1.93 15.79-8.15 22.01-6.05 6.3-13.78 9.66-24.02 9.66C16.32 69.35.36 53.89.36 34.91.36 15.93 16.32.47 35.3.47c10.5 0 17.98 4.12 23.6 9.49l-6.64 6.64c-4.03-3.78-9.49-6.72-16.97-6.72-13.86 0-24.7 11.17-24.7 25.03 0 13.86 10.84 25.03 24.7 25.03 8.99 0 14.11-3.61 17.39-6.89 2.66-2.66 4.41-6.46 5.1-11.65l-22.49-.01z", fill: "#4285F4" })
|
|
74
|
+
] });
|
|
75
|
+
}
|
|
76
|
+
function ReviewCard({ review, index }) {
|
|
77
|
+
const [expanded, setExpanded] = useState(false);
|
|
78
|
+
const shouldTruncate = review.text.length > 150;
|
|
79
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex h-full flex-col rounded-lg border bg-card p-5 shadow-sm", children: [
|
|
80
|
+
/* @__PURE__ */ jsxs("div", { className: "mb-3 flex items-center gap-3", children: [
|
|
81
|
+
review.photoUrl ? /* @__PURE__ */ jsx(
|
|
82
|
+
"img",
|
|
83
|
+
{
|
|
84
|
+
src: review.photoUrl,
|
|
85
|
+
alt: review.author,
|
|
86
|
+
className: "h-10 w-10 rounded-full object-cover"
|
|
87
|
+
}
|
|
88
|
+
) : /* @__PURE__ */ jsx(
|
|
89
|
+
"div",
|
|
90
|
+
{
|
|
91
|
+
className: "flex h-10 w-10 items-center justify-center rounded-full text-sm font-semibold text-white",
|
|
92
|
+
style: { backgroundColor: AVATAR_COLORS[index % AVATAR_COLORS.length] },
|
|
93
|
+
children: getInitial(review.author)
|
|
94
|
+
}
|
|
95
|
+
),
|
|
96
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
|
|
97
|
+
/* @__PURE__ */ jsx("p", { className: "truncate text-sm font-medium text-foreground", children: review.author }),
|
|
98
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: review.date })
|
|
99
|
+
] })
|
|
100
|
+
] }),
|
|
101
|
+
/* @__PURE__ */ jsx(StarRating, { rating: review.rating }),
|
|
102
|
+
/* @__PURE__ */ jsx("p", { className: "mt-3 flex-1 text-sm leading-relaxed text-foreground/80", children: shouldTruncate && !expanded ? review.text.slice(0, 150) + "\u2026" : review.text }),
|
|
103
|
+
shouldTruncate && /* @__PURE__ */ jsx(
|
|
104
|
+
"button",
|
|
105
|
+
{
|
|
106
|
+
type: "button",
|
|
107
|
+
onClick: () => setExpanded(!expanded),
|
|
108
|
+
className: cn(
|
|
109
|
+
"mt-1 inline-flex h-auto self-start p-0 text-xs font-medium text-primary underline-offset-4 hover:underline"
|
|
110
|
+
),
|
|
111
|
+
children: expanded ? "Show less" : "Read more"
|
|
112
|
+
}
|
|
113
|
+
)
|
|
114
|
+
] });
|
|
115
|
+
}
|
|
116
|
+
function GoogleReviewsCarousel({
|
|
117
|
+
reviews,
|
|
118
|
+
businessName,
|
|
119
|
+
placeUrl,
|
|
120
|
+
autoPlay = true,
|
|
121
|
+
autoPlaySpeed = 5e3
|
|
122
|
+
}) {
|
|
123
|
+
const [current, setCurrent] = useState(0);
|
|
124
|
+
const [paused, setPaused] = useState(false);
|
|
125
|
+
const total = reviews.length;
|
|
126
|
+
const next = useCallback(() => setCurrent((c) => (c + 1) % total), [total]);
|
|
127
|
+
const prev = useCallback(() => setCurrent((c) => (c - 1 + total) % total), [total]);
|
|
128
|
+
useEffect(() => {
|
|
129
|
+
if (!autoPlay || paused || total <= 1) return;
|
|
130
|
+
const id = setInterval(next, autoPlaySpeed);
|
|
131
|
+
return () => clearInterval(id);
|
|
132
|
+
}, [autoPlay, autoPlaySpeed, paused, next, total]);
|
|
133
|
+
if (total === 0) return null;
|
|
134
|
+
const avgRating = (reviews.reduce((s, r) => s + r.rating, 0) / total).toFixed(1);
|
|
135
|
+
return /* @__PURE__ */ jsxs(
|
|
136
|
+
"section",
|
|
137
|
+
{
|
|
138
|
+
className: "mx-auto w-full max-w-2xl",
|
|
139
|
+
onMouseEnter: () => setPaused(true),
|
|
140
|
+
onMouseLeave: () => setPaused(false),
|
|
141
|
+
children: [
|
|
142
|
+
/* @__PURE__ */ jsxs("div", { className: "mb-6 flex flex-col items-center gap-2 text-center", children: [
|
|
143
|
+
/* @__PURE__ */ jsx(GoogleLogo, { className: "h-7" }),
|
|
144
|
+
/* @__PURE__ */ jsxs("h3", { className: "text-lg font-semibold text-foreground", children: [
|
|
145
|
+
businessName,
|
|
146
|
+
" Reviews"
|
|
147
|
+
] }),
|
|
148
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
149
|
+
/* @__PURE__ */ jsx("span", { className: "text-2xl font-bold text-foreground", children: avgRating }),
|
|
150
|
+
/* @__PURE__ */ jsx(StarRating, { rating: Math.round(Number(avgRating)), size: 20 }),
|
|
151
|
+
/* @__PURE__ */ jsxs("span", { className: "text-sm text-muted-foreground", children: [
|
|
152
|
+
"(",
|
|
153
|
+
total,
|
|
154
|
+
" reviews)"
|
|
155
|
+
] })
|
|
156
|
+
] })
|
|
157
|
+
] }),
|
|
158
|
+
/* @__PURE__ */ jsxs("div", { className: "relative", children: [
|
|
159
|
+
total > 1 && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
160
|
+
/* @__PURE__ */ jsx(
|
|
161
|
+
"button",
|
|
162
|
+
{
|
|
163
|
+
type: "button",
|
|
164
|
+
className: cn(
|
|
165
|
+
"absolute -left-4 top-1/2 z-10 flex h-8 w-8 -translate-y-1/2 items-center justify-center rounded-full border border-input bg-background shadow-md ring-offset-background transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
|
|
166
|
+
),
|
|
167
|
+
onClick: prev,
|
|
168
|
+
"aria-label": "Previous review",
|
|
169
|
+
children: /* @__PURE__ */ jsx(ChevronLeft, { className: "h-4 w-4" })
|
|
170
|
+
}
|
|
171
|
+
),
|
|
172
|
+
/* @__PURE__ */ jsx(
|
|
173
|
+
"button",
|
|
174
|
+
{
|
|
175
|
+
type: "button",
|
|
176
|
+
className: cn(
|
|
177
|
+
"absolute -right-4 top-1/2 z-10 flex h-8 w-8 -translate-y-1/2 items-center justify-center rounded-full border border-input bg-background shadow-md ring-offset-background transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
|
|
178
|
+
),
|
|
179
|
+
onClick: next,
|
|
180
|
+
"aria-label": "Next review",
|
|
181
|
+
children: /* @__PURE__ */ jsx(ChevronRight, { className: "h-4 w-4" })
|
|
182
|
+
}
|
|
183
|
+
)
|
|
184
|
+
] }),
|
|
185
|
+
/* @__PURE__ */ jsx("div", { className: "overflow-hidden rounded-lg", children: /* @__PURE__ */ jsx(
|
|
186
|
+
"div",
|
|
187
|
+
{
|
|
188
|
+
className: "flex transition-transform duration-500 ease-in-out",
|
|
189
|
+
style: { transform: `translateX(-${current * 100}%)` },
|
|
190
|
+
children: reviews.map((review, i) => /* @__PURE__ */ jsx("div", { className: "w-full flex-shrink-0 px-1", children: /* @__PURE__ */ jsx(ReviewCard, { review, index: i }) }, i))
|
|
191
|
+
}
|
|
192
|
+
) }),
|
|
193
|
+
total > 1 && /* @__PURE__ */ jsx("div", { className: "mt-4 flex justify-center gap-2", children: reviews.map((_, i) => /* @__PURE__ */ jsx(
|
|
194
|
+
"button",
|
|
195
|
+
{
|
|
196
|
+
type: "button",
|
|
197
|
+
onClick: () => setCurrent(i),
|
|
198
|
+
"aria-label": `Go to review ${i + 1}`,
|
|
199
|
+
className: cn(
|
|
200
|
+
"h-2 w-2 rounded-full transition-all",
|
|
201
|
+
i === current ? "w-6 bg-primary" : "bg-muted-foreground/30 hover:bg-muted-foreground/50"
|
|
202
|
+
)
|
|
203
|
+
},
|
|
204
|
+
i
|
|
205
|
+
)) })
|
|
206
|
+
] }),
|
|
207
|
+
/* @__PURE__ */ jsx("div", { className: "mt-6 flex justify-center", children: /* @__PURE__ */ jsxs(
|
|
208
|
+
"a",
|
|
209
|
+
{
|
|
210
|
+
href: placeUrl,
|
|
211
|
+
target: "_blank",
|
|
212
|
+
rel: "noopener noreferrer",
|
|
213
|
+
className: cn(
|
|
214
|
+
"inline-flex h-11 items-center justify-center gap-2 whitespace-nowrap rounded-md border border-input bg-background px-4 py-2 font-medium ring-offset-background transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
|
|
215
|
+
),
|
|
216
|
+
children: [
|
|
217
|
+
"Read all reviews on Google",
|
|
218
|
+
/* @__PURE__ */ jsx(ExternalLink, { className: "ml-1 h-3.5 w-3.5 shrink-0" })
|
|
219
|
+
]
|
|
220
|
+
}
|
|
221
|
+
) })
|
|
222
|
+
]
|
|
223
|
+
}
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
export {
|
|
227
|
+
GoogleReviewsCarousel,
|
|
228
|
+
SAMPLE_REVIEWS
|
|
229
|
+
};
|
|
230
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/GoogleReviewsCarousel.tsx","../src/lib/cn.ts"],"sourcesContent":["import * as React from \"react\";\nimport { useState, useEffect, useCallback } from \"react\";\nimport { ChevronLeft, ChevronRight, Star, ExternalLink } from \"lucide-react\";\nimport { cn } from \"../lib/cn\";\n\n// ---------- Types ----------\nexport interface GoogleReview {\n\tauthor: string;\n\trating: number;\n\tdate: string;\n\ttext: string;\n\tphotoUrl?: string;\n}\n\nexport interface GoogleReviewsCarouselProps {\n\treviews: GoogleReview[];\n\tbusinessName: string;\n\tplaceUrl: string;\n\taccentColor?: string;\n\tautoPlay?: boolean;\n\tautoPlaySpeed?: number;\n}\n\n// ---------- Sample data ----------\nexport const SAMPLE_REVIEWS: GoogleReview[] = [\n\t{\n\t\tauthor: \"Sarah Mitchell\",\n\t\trating: 5,\n\t\tdate: \"2 weeks ago\",\n\t\ttext: \"Absolutely fantastic service! They fixed our burst pipe on a Sunday evening within an hour of calling. The plumber was professional, tidy, and explained everything clearly. Pricing was very fair — no hidden charges. Highly recommend to anyone needing reliable plumbing work.\",\n\t},\n\t{\n\t\tauthor: \"James Thornton\",\n\t\trating: 5,\n\t\tdate: \"1 month ago\",\n\t\ttext: \"Had a full rewire done on our 1930s semi. The team were punctual every single day, kept the house as clean as possible, and the finish is immaculate. They even helped us plan the socket layout for our new kitchen. Top-notch electricians.\",\n\t},\n\t{\n\t\tauthor: \"Maria Chen\",\n\t\trating: 5,\n\t\tdate: \"3 weeks ago\",\n\t\ttext: \"Called them for an emergency boiler repair in the middle of winter. They came out the same day and had the heating back on within two hours. Genuine lifesavers — we have young kids and couldn't have waited. Will use again for our annual service.\",\n\t},\n\t{\n\t\tauthor: \"David Okonkwo\",\n\t\trating: 4,\n\t\tdate: \"2 months ago\",\n\t\ttext: \"Great job installing our new bathroom. Tiling work is spotless and the underfloor heating is a dream. Only minor hiccup was a small delay waiting for parts, but they kept us updated throughout. Overall very happy with the result.\",\n\t},\n\t{\n\t\tauthor: \"Emma Richardson\",\n\t\trating: 5,\n\t\tdate: \"1 week ago\",\n\t\ttext: \"We've used this company three times now — blocked drain, leaking shower, and a full kitchen tap replacement. Every time the service has been prompt, friendly, and reasonably priced. It's so hard to find tradespeople you can trust, and these guys are the real deal.\",\n\t},\n];\n\n// ---------- Helpers ----------\nconst AVATAR_COLORS = [\n\t\"hsl(210, 60%, 50%)\",\n\t\"hsl(340, 60%, 50%)\",\n\t\"hsl(160, 55%, 42%)\",\n\t\"hsl(30, 70%, 50%)\",\n\t\"hsl(270, 50%, 55%)\",\n];\n\nfunction getInitial(name: string) {\n\treturn name.charAt(0).toUpperCase();\n}\n\nfunction StarRating({ rating, size = 16 }: { rating: number; size?: number }) {\n\treturn (\n\t\t<span className=\"inline-flex gap-0.5\">\n\t\t\t{Array.from({ length: 5 }).map((_, i) => (\n\t\t\t\t<Star\n\t\t\t\t\tkey={i}\n\t\t\t\t\tsize={size}\n\t\t\t\t\tclassName={i < rating ? \"fill-amber-400 text-amber-400\" : \"text-muted-foreground/30\"}\n\t\t\t\t/>\n\t\t\t))}\n\t\t</span>\n\t);\n}\n\nfunction GoogleLogo({ className }: { className?: string }) {\n\treturn (\n\t\t<svg className={className} viewBox=\"0 0 272 92\" xmlns=\"http://www.w3.org/2000/svg\">\n\t\t\t<path d=\"M115.75 47.18c0 12.77-9.99 22.18-22.25 22.18s-22.25-9.41-22.25-22.18C71.25 34.32 81.24 25 93.5 25s22.25 9.32 22.25 22.18zm-9.74 0c0-7.98-5.79-13.44-12.51-13.44S80.99 39.2 80.99 47.18c0 7.9 5.79 13.44 12.51 13.44s12.51-5.55 12.51-13.44z\" fill=\"#EA4335\"/>\n\t\t\t<path d=\"M163.75 47.18c0 12.77-9.99 22.18-22.25 22.18s-22.25-9.41-22.25-22.18c0-12.85 9.99-22.18 22.25-22.18s22.25 9.32 22.25 22.18zm-9.74 0c0-7.98-5.79-13.44-12.51-13.44s-12.51 5.46-12.51 13.44c0 7.9 5.79 13.44 12.51 13.44s12.51-5.55 12.51-13.44z\" fill=\"#FBBC05\"/>\n\t\t\t<path d=\"M209.75 26.34v39.82c0 16.38-9.66 23.07-21.08 23.07-10.75 0-17.22-7.19-19.66-13.07l8.48-3.53c1.51 3.61 5.21 7.87 11.17 7.87 7.31 0 11.84-4.51 11.84-13v-3.19h-.34c-2.18 2.69-6.38 5.04-11.68 5.04-11.09 0-21.25-9.66-21.25-22.09 0-12.52 10.16-22.26 21.25-22.26 5.29 0 9.49 2.35 11.68 4.96h.34v-3.61h9.25zm-8.56 20.92c0-7.81-5.21-13.52-11.84-13.52-6.72 0-12.35 5.71-12.35 13.52 0 7.73 5.63 13.36 12.35 13.36 6.63 0 11.84-5.63 11.84-13.36z\" fill=\"#4285F4\"/>\n\t\t\t<path d=\"M225 3v65h-9.5V3h9.5z\" fill=\"#34A853\"/>\n\t\t\t<path d=\"M262.02 54.48l7.56 5.04c-2.44 3.61-8.32 9.83-18.48 9.83-12.6 0-22.01-9.74-22.01-22.18 0-13.19 9.49-22.18 20.92-22.18 11.51 0 17.14 9.16 18.98 14.11l1.01 2.52-29.65 12.28c2.27 4.45 5.8 6.72 10.75 6.72 4.96 0 8.4-2.44 10.92-6.14zm-23.27-7.98l19.82-8.23c-1.09-2.77-4.37-4.7-8.23-4.7-4.95 0-11.84 4.37-11.59 12.93z\" fill=\"#EA4335\"/>\n\t\t\t<path d=\"M35.29 41.19V32H67c.31 1.64.47 3.58.47 5.68 0 7.06-1.93 15.79-8.15 22.01-6.05 6.3-13.78 9.66-24.02 9.66C16.32 69.35.36 53.89.36 34.91.36 15.93 16.32.47 35.3.47c10.5 0 17.98 4.12 23.6 9.49l-6.64 6.64c-4.03-3.78-9.49-6.72-16.97-6.72-13.86 0-24.7 11.17-24.7 25.03 0 13.86 10.84 25.03 24.7 25.03 8.99 0 14.11-3.61 17.39-6.89 2.66-2.66 4.41-6.46 5.1-11.65l-22.49-.01z\" fill=\"#4285F4\"/>\n\t\t</svg>\n\t);\n}\n\n// ---------- Review Card ----------\nfunction ReviewCard({ review, index }: { review: GoogleReview; index: number }) {\n\tconst [expanded, setExpanded] = useState(false);\n\tconst shouldTruncate = review.text.length > 150;\n\n\treturn (\n\t\t<div className=\"flex h-full flex-col rounded-lg border bg-card p-5 shadow-sm\">\n\t\t\t{/* Author row */}\n\t\t\t<div className=\"mb-3 flex items-center gap-3\">\n\t\t\t\t{review.photoUrl ? (\n\t\t\t\t\t<img\n\t\t\t\t\t\tsrc={review.photoUrl}\n\t\t\t\t\t\talt={review.author}\n\t\t\t\t\t\tclassName=\"h-10 w-10 rounded-full object-cover\"\n\t\t\t\t\t/>\n\t\t\t\t) : (\n\t\t\t\t\t<div\n\t\t\t\t\t\tclassName=\"flex h-10 w-10 items-center justify-center rounded-full text-sm font-semibold text-white\"\n\t\t\t\t\t\tstyle={{ backgroundColor: AVATAR_COLORS[index % AVATAR_COLORS.length] }}\n\t\t\t\t\t>\n\t\t\t\t\t\t{getInitial(review.author)}\n\t\t\t\t\t</div>\n\t\t\t\t)}\n\t\t\t\t<div className=\"min-w-0\">\n\t\t\t\t\t<p className=\"truncate text-sm font-medium text-foreground\">{review.author}</p>\n\t\t\t\t\t<p className=\"text-xs text-muted-foreground\">{review.date}</p>\n\t\t\t\t</div>\n\t\t\t</div>\n\n\t\t\t{/* Stars */}\n\t\t\t<StarRating rating={review.rating} />\n\n\t\t\t{/* Text */}\n\t\t\t<p className=\"mt-3 flex-1 text-sm leading-relaxed text-foreground/80\">\n\t\t\t\t{shouldTruncate && !expanded ? review.text.slice(0, 150) + \"…\" : review.text}\n\t\t\t</p>\n\t\t\t{shouldTruncate && (\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tonClick={() => setExpanded(!expanded)}\n\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\"mt-1 inline-flex h-auto self-start p-0 text-xs font-medium text-primary underline-offset-4 hover:underline\",\n\t\t\t\t\t)}\n\t\t\t\t>\n\t\t\t\t\t{expanded ? \"Show less\" : \"Read more\"}\n\t\t\t\t</button>\n\t\t\t)}\n\t\t</div>\n\t);\n}\n\n// ---------- Main Component ----------\nexport default function GoogleReviewsCarousel({\n\treviews,\n\tbusinessName,\n\tplaceUrl,\n\tautoPlay = true,\n\tautoPlaySpeed = 5000,\n}: GoogleReviewsCarouselProps) {\n\tconst [current, setCurrent] = useState(0);\n\tconst [paused, setPaused] = useState(false);\n\tconst total = reviews.length;\n\n\tconst next = useCallback(() => setCurrent((c) => (c + 1) % total), [total]);\n\tconst prev = useCallback(() => setCurrent((c) => (c - 1 + total) % total), [total]);\n\n\tuseEffect(() => {\n\t\tif (!autoPlay || paused || total <= 1) return;\n\t\tconst id = setInterval(next, autoPlaySpeed);\n\t\treturn () => clearInterval(id);\n\t}, [autoPlay, autoPlaySpeed, paused, next, total]);\n\n\tif (total === 0) return null;\n\n\tconst avgRating = (reviews.reduce((s, r) => s + r.rating, 0) / total).toFixed(1);\n\n\treturn (\n\t\t<section\n\t\t\tclassName=\"mx-auto w-full max-w-2xl\"\n\t\t\tonMouseEnter={() => setPaused(true)}\n\t\t\tonMouseLeave={() => setPaused(false)}\n\t\t>\n\t\t\t{/* Header */}\n\t\t\t<div className=\"mb-6 flex flex-col items-center gap-2 text-center\">\n\t\t\t\t<GoogleLogo className=\"h-7\" />\n\t\t\t\t<h3 className=\"text-lg font-semibold text-foreground\">\n\t\t\t\t\t{businessName} Reviews\n\t\t\t\t</h3>\n\t\t\t\t<div className=\"flex items-center gap-2\">\n\t\t\t\t\t<span className=\"text-2xl font-bold text-foreground\">{avgRating}</span>\n\t\t\t\t\t<StarRating rating={Math.round(Number(avgRating))} size={20} />\n\t\t\t\t\t<span className=\"text-sm text-muted-foreground\">({total} reviews)</span>\n\t\t\t\t</div>\n\t\t\t</div>\n\n\t\t\t{/* Carousel */}\n\t\t\t<div className=\"relative\">\n\t\t\t\t{/* Arrows */}\n\t\t\t\t{total > 1 && (\n\t\t\t\t\t<>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\t\t\"absolute -left-4 top-1/2 z-10 flex h-8 w-8 -translate-y-1/2 items-center justify-center rounded-full border border-input bg-background shadow-md ring-offset-background transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2\",\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\tonClick={prev}\n\t\t\t\t\t\t\taria-label=\"Previous review\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<ChevronLeft className=\"h-4 w-4\" />\n\t\t\t\t\t\t</button>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\t\t\"absolute -right-4 top-1/2 z-10 flex h-8 w-8 -translate-y-1/2 items-center justify-center rounded-full border border-input bg-background shadow-md ring-offset-background transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2\",\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\tonClick={next}\n\t\t\t\t\t\t\taria-label=\"Next review\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<ChevronRight className=\"h-4 w-4\" />\n\t\t\t\t\t\t</button>\n\t\t\t\t\t</>\n\t\t\t\t)}\n\n\t\t\t\t{/* Card viewport */}\n\t\t\t\t<div className=\"overflow-hidden rounded-lg\">\n\t\t\t\t\t<div\n\t\t\t\t\t\tclassName=\"flex transition-transform duration-500 ease-in-out\"\n\t\t\t\t\t\tstyle={{ transform: `translateX(-${current * 100}%)` }}\n\t\t\t\t\t>\n\t\t\t\t\t\t{reviews.map((review, i) => (\n\t\t\t\t\t\t\t<div key={i} className=\"w-full flex-shrink-0 px-1\">\n\t\t\t\t\t\t\t\t<ReviewCard review={review} index={i} />\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t))}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\n\t\t\t\t{/* Dots */}\n\t\t\t\t{total > 1 && (\n\t\t\t\t\t<div className=\"mt-4 flex justify-center gap-2\">\n\t\t\t\t\t\t{reviews.map((_, i) => (\n\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\tkey={i}\n\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\tonClick={() => setCurrent(i)}\n\t\t\t\t\t\t\t\taria-label={`Go to review ${i + 1}`}\n\t\t\t\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\t\t\t\"h-2 w-2 rounded-full transition-all\",\n\t\t\t\t\t\t\t\t\ti === current\n\t\t\t\t\t\t\t\t\t\t? \"w-6 bg-primary\"\n\t\t\t\t\t\t\t\t\t\t: \"bg-muted-foreground/30 hover:bg-muted-foreground/50\",\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t))}\n\t\t\t\t\t</div>\n\t\t\t\t)}\n\t\t\t</div>\n\n\t\t\t{/* CTA */}\n\t\t\t<div className=\"mt-6 flex justify-center\">\n\t\t\t\t<a\n\t\t\t\t\thref={placeUrl}\n\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\trel=\"noopener noreferrer\"\n\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\"inline-flex h-11 items-center justify-center gap-2 whitespace-nowrap rounded-md border border-input bg-background px-4 py-2 font-medium ring-offset-background transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2\",\n\t\t\t\t\t)}\n\t\t\t\t>\n\t\t\t\t\tRead all reviews on Google\n\t\t\t\t\t<ExternalLink className=\"ml-1 h-3.5 w-3.5 shrink-0\" />\n\t\t\t\t</a>\n\t\t\t</div>\n\t\t</section>\n\t);\n}\n","import { type ClassValue, clsx } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n\treturn twMerge(clsx(inputs));\n}\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kern-di/trust-carousel",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Drop-in Google Reviews carousel component for embedding social proof on any site",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"sideEffects": false,
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"clsx": "^2.1.1",
|
|
21
|
+
"tailwind-merge": "^2.6.0"
|
|
22
|
+
},
|
|
23
|
+
"peerDependencies": {
|
|
24
|
+
"lucide-react": ">=0.400.0",
|
|
25
|
+
"react": "^18.0.0",
|
|
26
|
+
"react-dom": "^18.0.0"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/node": "^22.19.15",
|
|
30
|
+
"@types/react": "^18.3.28",
|
|
31
|
+
"@types/react-dom": "^18.3.7",
|
|
32
|
+
"@vitejs/plugin-react-swc": "^3.11.0",
|
|
33
|
+
"autoprefixer": "^10.4.21",
|
|
34
|
+
"lucide-react": "^0.511.0",
|
|
35
|
+
"react": "^18.3.1",
|
|
36
|
+
"react-dom": "^18.3.1",
|
|
37
|
+
"tailwindcss": "^3.4.17",
|
|
38
|
+
"tsup": "^8.5.1",
|
|
39
|
+
"typescript": "^5.9.3",
|
|
40
|
+
"vite": "^5.4.21",
|
|
41
|
+
"@kern/ui": "0.0.0"
|
|
42
|
+
},
|
|
43
|
+
"keywords": [
|
|
44
|
+
"react",
|
|
45
|
+
"kern",
|
|
46
|
+
"google-reviews",
|
|
47
|
+
"carousel",
|
|
48
|
+
"social-proof"
|
|
49
|
+
],
|
|
50
|
+
"license": "MIT",
|
|
51
|
+
"publishConfig": {
|
|
52
|
+
"access": "public"
|
|
53
|
+
},
|
|
54
|
+
"scripts": {
|
|
55
|
+
"build": "tsup",
|
|
56
|
+
"demo": "vite --config vite.demo.config.ts",
|
|
57
|
+
"watch": "tsup --watch",
|
|
58
|
+
"type-check": "tsc --noEmit",
|
|
59
|
+
"publish:npm": "node ./scripts/publish.mjs",
|
|
60
|
+
"release": "pnpm version patch --no-git-tag-version && pnpm publish:npm",
|
|
61
|
+
"release:publish": "pnpm publish:npm"
|
|
62
|
+
}
|
|
63
|
+
}
|