@stackshift-ui/testimonial 6.0.2 → 6.0.4-beta.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/chunk-3NRVKPEL.mjs +1 -0
- package/dist/chunk-EELHY35O.mjs +1 -0
- package/dist/chunk-GR5YMBLP.mjs +1 -0
- package/dist/chunk-ISQGJX3U.mjs +1 -0
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/dist/testimonial.js +1 -1
- package/dist/testimonial.mjs +1 -1
- package/dist/testimonial_a.js +1 -1
- package/dist/testimonial_a.mjs +1 -1
- package/dist/testimonial_b.js +1 -1
- package/dist/testimonial_b.mjs +1 -1
- package/dist/testimonial_c.js +1 -1
- package/dist/testimonial_c.mjs +1 -1
- package/dist/testimonial_d.js +1 -1
- package/dist/testimonial_d.mjs +1 -1
- package/package.json +16 -15
- package/src/index.ts +8 -0
- package/src/testimonial.test.tsx +13 -0
- package/src/testimonial.tsx +32 -0
- package/src/testimonial_a.tsx +111 -0
- package/src/testimonial_b.tsx +158 -0
- package/src/testimonial_c.tsx +138 -0
- package/src/testimonial_d.tsx +149 -0
- package/src/types.ts +412 -0
- package/dist/chunk-3BGNGOU3.mjs +0 -1
- package/dist/chunk-EESOVMOP.mjs +0 -1
- package/dist/chunk-K333RZRM.mjs +0 -1
- package/dist/chunk-RWEMGWOQ.mjs +0 -1
- /package/dist/{chunk-S6UYPLFX.mjs → chunk-DYY7BCSA.mjs} +0 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { Avatar } from "@stackshift-ui/avatar";
|
|
2
|
+
import { Card } from "@stackshift-ui/card";
|
|
3
|
+
import { Container } from "@stackshift-ui/container";
|
|
4
|
+
import { Flex } from "@stackshift-ui/flex";
|
|
5
|
+
import { Heading } from "@stackshift-ui/heading";
|
|
6
|
+
import { Section } from "@stackshift-ui/section";
|
|
7
|
+
import { SwiperPagination } from "@stackshift-ui/swiper-pagination";
|
|
8
|
+
import { Text } from "@stackshift-ui/text";
|
|
9
|
+
import React from "react";
|
|
10
|
+
import { TestimonialProps } from ".";
|
|
11
|
+
import { Testimonial as iTestimonial } from "./types";
|
|
12
|
+
|
|
13
|
+
export default function Testimonial_A({ testimonials }: TestimonialProps) {
|
|
14
|
+
const [testimony, setTestimony] = React.useState(0);
|
|
15
|
+
|
|
16
|
+
const slider = (index: number) => {
|
|
17
|
+
setTestimony(index);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<Section className="py-20 bg-background">
|
|
22
|
+
<Container maxWidth={1280}>
|
|
23
|
+
<Card className="py-10" borderRadius="md">
|
|
24
|
+
<Flex wrap align="center" justify="center" className="max-w-5xl p-4">
|
|
25
|
+
<AvatarSection testimonials={testimonials} testimony={testimony} />
|
|
26
|
+
<TestimonyContent testimonials={testimonials} testimony={testimony} slider={slider} />
|
|
27
|
+
</Flex>
|
|
28
|
+
</Card>
|
|
29
|
+
</Container>
|
|
30
|
+
</Section>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function AvatarSection({
|
|
35
|
+
testimonials,
|
|
36
|
+
testimony,
|
|
37
|
+
}: {
|
|
38
|
+
testimonials?: iTestimonial[];
|
|
39
|
+
testimony: number;
|
|
40
|
+
}) {
|
|
41
|
+
if (!testimonials?.[testimony]) return null;
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<div className="w-full mb-6 text-center lg:w-1/3">
|
|
45
|
+
{testimonials?.[testimony]?.mainImage?.image ? (
|
|
46
|
+
<Avatar
|
|
47
|
+
className={"border-0 mx-auto"}
|
|
48
|
+
size={128}
|
|
49
|
+
alt={
|
|
50
|
+
testimonials?.[testimony]?.mainImage?.alt ??
|
|
51
|
+
`testimonial-source-${testimonials?.[testimony]?.name}-profile-image`
|
|
52
|
+
}
|
|
53
|
+
src={`${testimonials?.[testimony]?.mainImage?.image}`}
|
|
54
|
+
/>
|
|
55
|
+
) : null}
|
|
56
|
+
{testimonials?.[testimony]?.name ? (
|
|
57
|
+
<Text className="text-xl">{testimonials?.[testimony]?.name}</Text>
|
|
58
|
+
) : null}
|
|
59
|
+
{testimonials?.[testimony]?.jobTitle ? (
|
|
60
|
+
<Text className="text-primary">{testimonials?.[testimony]?.jobTitle}</Text>
|
|
61
|
+
) : null}
|
|
62
|
+
</div>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function TestimonyContent({
|
|
67
|
+
testimonials,
|
|
68
|
+
testimony,
|
|
69
|
+
slider,
|
|
70
|
+
}: {
|
|
71
|
+
testimonials?: any;
|
|
72
|
+
testimony: number;
|
|
73
|
+
slider: (index: number) => void;
|
|
74
|
+
}) {
|
|
75
|
+
if (!testimonials?.[testimony]) return null;
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<div className="w-full lg:w-2/3">
|
|
79
|
+
<svg
|
|
80
|
+
className="w-10 h-10 mb-4 text-primary"
|
|
81
|
+
viewBox="0 0 32 28"
|
|
82
|
+
fill="none"
|
|
83
|
+
xmlns="http://www.w3.org/2000/svg">
|
|
84
|
+
<path
|
|
85
|
+
d="M10.2418 12.749C9.45369 12.522 8.66554 12.4069 7.89887 12.4069C6.71496 12.4069 5.72709 12.6775 4.96109 13.0088C5.69957 10.3053 7.47358 5.6405 11.0075 5.11517C11.3348 5.0665 11.603 4.82986 11.6923 4.51131L12.4646 1.74875C12.5298 1.51512 12.4912 1.26505 12.3579 1.06231C12.2246 0.859563 12.0105 0.724288 11.7705 0.691393C11.5097 0.655812 11.2438 0.637686 10.9803 0.637686C6.73846 0.637686 2.53756 5.06516 0.764895 11.4046C-0.275679 15.1238 -0.580802 20.7154 1.98237 24.2349C3.41668 26.2043 5.50924 27.2559 8.20198 27.361C8.21305 27.3613 8.2238 27.3616 8.23487 27.3616C11.5573 27.3616 14.5035 25.1241 15.3997 21.9208C15.9351 20.0058 15.6931 17.9975 14.7176 16.2644C13.7526 14.5508 12.1632 13.3018 10.2418 12.749Z"
|
|
86
|
+
fill="currentColor"
|
|
87
|
+
/>
|
|
88
|
+
<path
|
|
89
|
+
d="M31.0396 16.2648C30.0746 14.5508 28.4852 13.3018 26.5638 12.749C25.7757 12.522 24.9875 12.4069 24.2212 12.4069C23.0373 12.4069 22.0491 12.6775 21.2831 13.0088C22.0215 10.3053 23.7955 5.6405 27.3298 5.11517C27.6571 5.0665 27.9249 4.82986 28.0146 4.51131L28.7869 1.74875C28.8521 1.51512 28.8135 1.26505 28.6802 1.06231C28.5473 0.859563 28.3331 0.724288 28.0928 0.691393C27.8323 0.655812 27.5664 0.637686 27.3026 0.637686C23.0608 0.637686 18.8599 5.06516 17.0869 11.4046C16.0466 15.1238 15.7415 20.7154 18.305 24.2356C19.739 26.2046 21.8319 27.2566 24.5243 27.3613C24.5354 27.3616 24.5461 27.362 24.5575 27.362C27.8796 27.362 30.8261 25.1244 31.7224 21.9211C32.2571 20.0061 32.0147 17.9975 31.0396 16.2648Z"
|
|
90
|
+
fill="currentColor"
|
|
91
|
+
/>
|
|
92
|
+
</svg>
|
|
93
|
+
<Heading>{testimonials?.[testimony]?.testimony}</Heading>
|
|
94
|
+
{testimonials?.length > 1 ? (
|
|
95
|
+
<React.Fragment>
|
|
96
|
+
{testimonials.map((item: iTestimonial, index: number) => (
|
|
97
|
+
<SwiperPagination
|
|
98
|
+
colorScheme="blue"
|
|
99
|
+
isActive={index === testimony}
|
|
100
|
+
ariaLabel={`Show Testimonial ${index}`}
|
|
101
|
+
key={index}
|
|
102
|
+
onClick={() => slider(index)}
|
|
103
|
+
/>
|
|
104
|
+
))}
|
|
105
|
+
</React.Fragment>
|
|
106
|
+
) : null}
|
|
107
|
+
</div>
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export { Testimonial_A };
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { Card } from "@stackshift-ui/card";
|
|
2
|
+
import { Container } from "@stackshift-ui/container";
|
|
3
|
+
import { Flex } from "@stackshift-ui/flex";
|
|
4
|
+
import { Heading } from "@stackshift-ui/heading";
|
|
5
|
+
import { Section } from "@stackshift-ui/section";
|
|
6
|
+
import { SwiperButton } from "@stackshift-ui/swiper-button";
|
|
7
|
+
import { Text } from "@stackshift-ui/text";
|
|
8
|
+
import React, { useState } from "react";
|
|
9
|
+
import { TestimonialProps } from ".";
|
|
10
|
+
import { Testimonial as iTestimonial } from "./types";
|
|
11
|
+
|
|
12
|
+
export default function Testimonial_B({ caption, title, testimonials }: TestimonialProps) {
|
|
13
|
+
const [testimony, setTestimony] = useState(testimonials);
|
|
14
|
+
|
|
15
|
+
const slider = (action: "next" | "prev") => {
|
|
16
|
+
setTestimony(prevState => {
|
|
17
|
+
// Ensure prevState is always an array
|
|
18
|
+
const currentState = prevState || [];
|
|
19
|
+
|
|
20
|
+
if (action === "next") {
|
|
21
|
+
// Remove first element
|
|
22
|
+
const firstItem = currentState.length > 0 ? currentState.shift() : undefined;
|
|
23
|
+
|
|
24
|
+
// If there was a first item, push it to the last index
|
|
25
|
+
return firstItem ? [...currentState, firstItem] : currentState;
|
|
26
|
+
} else if (action === "prev") {
|
|
27
|
+
// Remove last element
|
|
28
|
+
const lastItem = currentState.length > 0 ? currentState.pop() : undefined;
|
|
29
|
+
|
|
30
|
+
// If there was a last item, push it to the first index
|
|
31
|
+
return lastItem ? [lastItem, ...currentState] : currentState;
|
|
32
|
+
}
|
|
33
|
+
return currentState; // Return the current state if no action is matched
|
|
34
|
+
});
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<Section className="py-20 overflow-hidden bg-background">
|
|
39
|
+
<Container maxWidth={1280}>
|
|
40
|
+
<Flex wrap align="center" justify="center" className="pt-8 pb-16">
|
|
41
|
+
<SwiperControl testimony={testimony} slider={slider}>
|
|
42
|
+
<TestimonialHeader caption={caption} title={title} />
|
|
43
|
+
</SwiperControl>
|
|
44
|
+
</Flex>
|
|
45
|
+
<TestimonialList testimonials={testimony} />
|
|
46
|
+
</Container>
|
|
47
|
+
</Section>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function TestimonialHeader({ caption, title }: { caption?: string; title?: string }) {
|
|
52
|
+
return (
|
|
53
|
+
<Container maxWidth={576} className="mb-10 text-center ">
|
|
54
|
+
<Text weight="bold" className="text-secondary">
|
|
55
|
+
{caption}
|
|
56
|
+
</Text>
|
|
57
|
+
<Heading fontSize="3xl" className="mt-4">
|
|
58
|
+
{title}
|
|
59
|
+
</Heading>
|
|
60
|
+
</Container>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function SwiperControl({
|
|
65
|
+
testimony,
|
|
66
|
+
slider,
|
|
67
|
+
children,
|
|
68
|
+
}: {
|
|
69
|
+
testimony?: iTestimonial[];
|
|
70
|
+
slider: (action: "next" | "prev") => void;
|
|
71
|
+
children: React.ReactNode;
|
|
72
|
+
}) {
|
|
73
|
+
if (!testimony) return null;
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<div className="flex items-center justify-between w-full">
|
|
77
|
+
{/* SHOW PREV */}
|
|
78
|
+
{testimony?.length >= 4 && (
|
|
79
|
+
<SwiperButton
|
|
80
|
+
type="left"
|
|
81
|
+
className="p-4 bg-white"
|
|
82
|
+
onClick={() => slider("prev")}
|
|
83
|
+
ariaLabel="Show previous testimonial"
|
|
84
|
+
/>
|
|
85
|
+
)}
|
|
86
|
+
<div className="flex-1 text-center">{children}</div>
|
|
87
|
+
{/* SHOW NEXT */}
|
|
88
|
+
{testimony?.length >= 4 && (
|
|
89
|
+
<SwiperButton
|
|
90
|
+
type="right"
|
|
91
|
+
className="p-4 bg-white"
|
|
92
|
+
onClick={() => slider("next")}
|
|
93
|
+
ariaLabel="Show next testimonial"
|
|
94
|
+
/>
|
|
95
|
+
)}
|
|
96
|
+
</div>
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function TestimonialList({ testimonials }: { testimonials?: iTestimonial[] }) {
|
|
101
|
+
if (!testimonials) return null;
|
|
102
|
+
|
|
103
|
+
return (
|
|
104
|
+
<div className="relative w-full">
|
|
105
|
+
<Flex wrap justify="center" className="mx-auto ">
|
|
106
|
+
{testimonials.slice(0, 3).map((testimonial: iTestimonial, index: number) => (
|
|
107
|
+
<TestimonialItem key={index} {...testimonial} />
|
|
108
|
+
))}
|
|
109
|
+
</Flex>
|
|
110
|
+
</div>
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function TestimonialItem({
|
|
115
|
+
testimony,
|
|
116
|
+
name,
|
|
117
|
+
jobTitle,
|
|
118
|
+
}: {
|
|
119
|
+
testimony?: string;
|
|
120
|
+
name?: string;
|
|
121
|
+
jobTitle?: string;
|
|
122
|
+
}) {
|
|
123
|
+
if (!testimony) return null;
|
|
124
|
+
|
|
125
|
+
return (
|
|
126
|
+
<div className="mb-4 px-3 w-full lg:w-1/3">
|
|
127
|
+
<Card className="p-5" borderRadius="md">
|
|
128
|
+
<QuoteIcon />
|
|
129
|
+
<Text className="mb-4 leading-loose" muted>
|
|
130
|
+
{testimony}
|
|
131
|
+
</Text>
|
|
132
|
+
<Text weight="bold">{name}</Text>
|
|
133
|
+
<Text muted>{jobTitle}</Text>
|
|
134
|
+
</Card>
|
|
135
|
+
</div>
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function QuoteIcon() {
|
|
140
|
+
return (
|
|
141
|
+
<svg
|
|
142
|
+
className="w-8 h-8 mb-6 text-primary"
|
|
143
|
+
viewBox="0 0 32 28"
|
|
144
|
+
fill="none"
|
|
145
|
+
xmlns="http://www.w3.org/2000/svg">
|
|
146
|
+
<path
|
|
147
|
+
d="M10.2418 12.749C9.45369 12.522 8.66554 12.4069 7.89887 12.4069C6.71496 12.4069 5.72709 12.6775 4.96109 13.0088C5.69957 10.3053 7.47358 5.6405 11.0075 5.11517C11.3348 5.0665 11.603 4.82986 11.6923 4.51131L12.4646 1.74875C12.5298 1.51512 12.4912 1.26505 12.3579 1.06231C12.2246 0.859563 12.0105 0.724288 11.7705 0.691393C11.5097 0.655812 11.2438 0.637686 10.9803 0.637686C6.73846 0.637686 2.53756 5.06516 0.764895 11.4046C-0.275679 15.1238 -0.580802 20.7154 1.98237 24.2349C3.41668 26.2043 5.50924 27.2559 8.20198 27.361C8.21305 27.3613 8.2238 27.3616 8.23487 27.3616C11.5573 27.3616 14.5035 25.1241 15.3997 21.9208C15.9351 20.0058 15.6931 17.9975 14.7176 16.2644C13.7526 14.5508 12.1632 13.3018 10.2418 12.749Z"
|
|
148
|
+
fill="currentColor"
|
|
149
|
+
/>
|
|
150
|
+
<path
|
|
151
|
+
d="M31.0396 16.2648C30.0746 14.5508 28.4852 13.3018 26.5638 12.749C25.7757 12.522 24.9875 12.4069 24.2212 12.4069C23.0373 12.4069 22.0491 12.6775 21.2831 13.0088C22.0215 10.3053 23.7955 5.6405 27.3298 5.11517C27.6571 5.0665 27.9249 4.82986 28.0146 4.51131L28.7869 1.74875C28.8521 1.51512 28.8135 1.26505 28.6802 1.06231C28.5473 0.859563 28.3331 0.724288 28.0928 0.691393C27.8323 0.655812 27.5664 0.637686 27.3026 0.637686C23.0608 0.637686 18.8599 5.06516 17.0869 11.4046C16.0466 15.1238 15.7415 20.7154 18.305 24.2356C19.739 26.2046 21.8319 27.2566 24.5243 27.3613C24.5354 27.3616 24.5461 27.362 24.5575 27.362C27.8796 27.362 30.8261 25.1244 31.7224 21.9211C32.2571 20.0061 32.0147 17.9975 31.0396 16.2648Z"
|
|
152
|
+
fill="currentColor"
|
|
153
|
+
/>
|
|
154
|
+
</svg>
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export { Testimonial_B };
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { Avatar } from "@stackshift-ui/avatar";
|
|
2
|
+
import { Card } from "@stackshift-ui/card";
|
|
3
|
+
import { Container } from "@stackshift-ui/container";
|
|
4
|
+
import { Flex } from "@stackshift-ui/flex";
|
|
5
|
+
import { Heading } from "@stackshift-ui/heading";
|
|
6
|
+
import { Section } from "@stackshift-ui/section";
|
|
7
|
+
import { SwiperButton } from "@stackshift-ui/swiper-button";
|
|
8
|
+
import { Text } from "@stackshift-ui/text";
|
|
9
|
+
import React from "react";
|
|
10
|
+
import { TestimonialProps } from ".";
|
|
11
|
+
import { Testimonial as iTestimonial } from "./types";
|
|
12
|
+
|
|
13
|
+
export default function Testimonial_C({ caption, title, testimonials }: TestimonialProps) {
|
|
14
|
+
const [testimony, setTestimony] = React.useState(testimonials);
|
|
15
|
+
|
|
16
|
+
const slider = (action: "next" | "prev") => {
|
|
17
|
+
setTestimony(prevState => {
|
|
18
|
+
if (!prevState || prevState.length === 0) return prevState;
|
|
19
|
+
|
|
20
|
+
// Create a new array to avoid mutating the original state
|
|
21
|
+
const updatedTestimony = [...prevState];
|
|
22
|
+
|
|
23
|
+
if (action === "next") {
|
|
24
|
+
// Remove the first element and push it to the end
|
|
25
|
+
const firstItem = updatedTestimony.shift();
|
|
26
|
+
if (firstItem) updatedTestimony.push(firstItem);
|
|
27
|
+
} else if (action === "prev") {
|
|
28
|
+
// Remove the last element and push it to the front
|
|
29
|
+
const lastItem = updatedTestimony.pop();
|
|
30
|
+
if (lastItem) updatedTestimony.unshift(lastItem);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return updatedTestimony;
|
|
34
|
+
});
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<Section className="py-10 overflow-hidden bg-background lg:py-20">
|
|
39
|
+
<Container className="pb-6 lg:pb-16" maxWidth={1280}>
|
|
40
|
+
<Flex
|
|
41
|
+
wrap
|
|
42
|
+
align="center"
|
|
43
|
+
justify="center"
|
|
44
|
+
className="text-center lg:justify-between lg:text-left">
|
|
45
|
+
<TestimonialHeader caption={caption} title={title} />
|
|
46
|
+
<TestimonialSwiper testimony={testimony} slider={slider} />
|
|
47
|
+
</Flex>
|
|
48
|
+
</Container>
|
|
49
|
+
<TestimonialList testimony={testimony} />
|
|
50
|
+
</Section>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function TestimonialHeader({ caption, title }: { caption?: string; title?: string }) {
|
|
55
|
+
return (
|
|
56
|
+
<div className="w-full mb-4 lg:mb-0 lg:w-4/5">
|
|
57
|
+
<Text weight="bold" className="text-secondary">
|
|
58
|
+
{caption}
|
|
59
|
+
</Text>
|
|
60
|
+
<Heading fontSize="3xl">{title}</Heading>
|
|
61
|
+
</div>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function TestimonialSwiper({
|
|
66
|
+
testimony,
|
|
67
|
+
slider,
|
|
68
|
+
}: {
|
|
69
|
+
testimony?: iTestimonial[];
|
|
70
|
+
slider: (action: "next" | "prev") => void;
|
|
71
|
+
}) {
|
|
72
|
+
if (!testimony) return null;
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<div className="w-full lg:w-1/5">
|
|
76
|
+
{testimony && testimony?.length >= 4 && (
|
|
77
|
+
<SwiperButton
|
|
78
|
+
type="left"
|
|
79
|
+
className="order-last p-5 mr-4 bg-white lg:order-first"
|
|
80
|
+
onClick={() => slider("prev")}
|
|
81
|
+
ariaLabel="Show previous testimonial"
|
|
82
|
+
/>
|
|
83
|
+
)}
|
|
84
|
+
{testimony && testimony?.length >= 4 && (
|
|
85
|
+
<SwiperButton
|
|
86
|
+
type="right"
|
|
87
|
+
className="order-last p-5 bg-white"
|
|
88
|
+
onClick={() => slider("next")}
|
|
89
|
+
ariaLabel="Show next testimonial"
|
|
90
|
+
/>
|
|
91
|
+
)}
|
|
92
|
+
</div>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function TestimonialList({ testimony }: { testimony?: iTestimonial[] }) {
|
|
97
|
+
if (!testimony) return null;
|
|
98
|
+
|
|
99
|
+
return (
|
|
100
|
+
<Flex className="relative">
|
|
101
|
+
{testimony && (
|
|
102
|
+
<Flex wrap className="max-w-6xl px-2 mx-auto">
|
|
103
|
+
{testimony
|
|
104
|
+
?.slice(0, 3)
|
|
105
|
+
?.map((item, index) => <TestimonialItem item={item} index={index} key={item?._key} />)}
|
|
106
|
+
</Flex>
|
|
107
|
+
)}
|
|
108
|
+
</Flex>
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function TestimonialItem({ item, index }: { item?: iTestimonial; index: number }) {
|
|
113
|
+
if (!item) return null;
|
|
114
|
+
|
|
115
|
+
return (
|
|
116
|
+
<div className="w-full px-3 mb-4 lg:w-1/3" key={index}>
|
|
117
|
+
<Card className="p-8 text-center" borderRadius="md">
|
|
118
|
+
<Text className="mb-8 leading-loose" muted>
|
|
119
|
+
{item?.testimony}
|
|
120
|
+
</Text>
|
|
121
|
+
{item?.mainImage?.image && (
|
|
122
|
+
<Avatar
|
|
123
|
+
size={48}
|
|
124
|
+
className="mx-auto border-0"
|
|
125
|
+
src={`${item?.mainImage?.image}`}
|
|
126
|
+
alt={item?.mainImage?.alt ?? `testimonial-source-${item?.name}-profile-image`}
|
|
127
|
+
/>
|
|
128
|
+
)}
|
|
129
|
+
<Text className="mb-1" fontSize="2xl" weight="bold">
|
|
130
|
+
{item?.name}
|
|
131
|
+
</Text>
|
|
132
|
+
<Text muted>{item?.jobTitle}</Text>
|
|
133
|
+
</Card>
|
|
134
|
+
</div>
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export { Testimonial_C };
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { Card } from "@stackshift-ui/card";
|
|
2
|
+
import { Container } from "@stackshift-ui/container";
|
|
3
|
+
import { Flex } from "@stackshift-ui/flex";
|
|
4
|
+
import { Image } from "@stackshift-ui/image";
|
|
5
|
+
import { Section } from "@stackshift-ui/section";
|
|
6
|
+
import { SwiperButton } from "@stackshift-ui/swiper-button";
|
|
7
|
+
import { Text } from "@stackshift-ui/text";
|
|
8
|
+
import React from "react";
|
|
9
|
+
import { TestimonialProps } from ".";
|
|
10
|
+
|
|
11
|
+
export default function Testimonial_D({ testimonials }: TestimonialProps) {
|
|
12
|
+
const [currentIndex, setCurrentIndex] = React.useState(0);
|
|
13
|
+
|
|
14
|
+
const handleSlider = (direction: "prev" | "next") => {
|
|
15
|
+
if (!testimonials || testimonials.length <= 1) return;
|
|
16
|
+
if (direction === "next") {
|
|
17
|
+
setCurrentIndex(prev => (prev !== testimonials.length - 1 ? prev + 1 : 0));
|
|
18
|
+
} else {
|
|
19
|
+
setCurrentIndex(prev => (prev > 0 ? prev - 1 : testimonials.length - 1));
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const getRatingToArray = (rating: number) => {
|
|
24
|
+
const num = [];
|
|
25
|
+
|
|
26
|
+
for (let i = 0; i < rating; i++) {
|
|
27
|
+
num.push(i);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return num;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<Section className="py-20 bg-background">
|
|
35
|
+
<Container maxWidth={1280}>
|
|
36
|
+
<div className="items-center justify-center md:space-x-8 lg:flex">
|
|
37
|
+
<div className="mb-10 text-center lg:hidden">
|
|
38
|
+
{testimonials && testimonials?.length > 1 && (
|
|
39
|
+
<SwiperButton
|
|
40
|
+
type="left"
|
|
41
|
+
className="p-4 mr-6 bg-white lg:order-first lg:mr-0"
|
|
42
|
+
onClick={() => handleSlider("prev")}
|
|
43
|
+
ariaLabel="Show previous testimonial"
|
|
44
|
+
/>
|
|
45
|
+
)}
|
|
46
|
+
{testimonials && testimonials?.length > 1 && (
|
|
47
|
+
<SwiperButton
|
|
48
|
+
type="right"
|
|
49
|
+
className="p-4 mr-6 bg-white lg:order-first lg:mr-0"
|
|
50
|
+
onClick={() => handleSlider("next")}
|
|
51
|
+
ariaLabel="Show next testimonial"
|
|
52
|
+
/>
|
|
53
|
+
)}
|
|
54
|
+
</div>
|
|
55
|
+
{testimonials && testimonials?.length > 1 && (
|
|
56
|
+
<SwiperButton
|
|
57
|
+
type="left"
|
|
58
|
+
className="hidden p-4 mr-6 bg-white lg:block lg:order-first lg:mr-0"
|
|
59
|
+
onClick={() => handleSlider("prev")}
|
|
60
|
+
ariaLabel="Show previous testimonial"
|
|
61
|
+
/>
|
|
62
|
+
)}
|
|
63
|
+
{testimonials?.[currentIndex] && (
|
|
64
|
+
<Card className="flex flex-wrap w-full" borderRadius="md">
|
|
65
|
+
{testimonials?.[currentIndex]?.rating && (
|
|
66
|
+
<div className="w-full py-10 text-center border-b lg:border-r lg:border-b-0 lg:w-1/3">
|
|
67
|
+
<span className="text-5xl font-bold lg:text-6xl">
|
|
68
|
+
{`${testimonials?.[currentIndex]?.rating}.0`}
|
|
69
|
+
</span>
|
|
70
|
+
<Flex align="center" justify="center" className="mb-6 text-primary">
|
|
71
|
+
{testimonials?.[currentIndex]?.rating !== undefined &&
|
|
72
|
+
getRatingToArray(Number(testimonials?.[currentIndex]?.rating)).map(
|
|
73
|
+
(_, idx) => <RatingIcon key={idx} />,
|
|
74
|
+
)}
|
|
75
|
+
</Flex>
|
|
76
|
+
<div className="object-contain w-32 h-24 mx-auto mb-6 rounded-full">
|
|
77
|
+
{testimonials[currentIndex]?.mainImage?.image && (
|
|
78
|
+
<Image
|
|
79
|
+
className="h-[96px] w-[128px] object-scale-down"
|
|
80
|
+
src={`${testimonials[currentIndex]?.mainImage?.image}`}
|
|
81
|
+
width={128}
|
|
82
|
+
height={96}
|
|
83
|
+
alt={
|
|
84
|
+
testimonials[currentIndex]?.mainImage?.alt ??
|
|
85
|
+
`testimonial-source-profile-image${currentIndex}`
|
|
86
|
+
}
|
|
87
|
+
/>
|
|
88
|
+
)}
|
|
89
|
+
</div>
|
|
90
|
+
</div>
|
|
91
|
+
)}
|
|
92
|
+
<div className="w-full px-6 py-10 lg:w-2/3">
|
|
93
|
+
<QuoteIcon />
|
|
94
|
+
<Text muted className="mb-10 text-xl leading-loose lg:text-2xl">
|
|
95
|
+
{testimonials[currentIndex]?.testimony}
|
|
96
|
+
</Text>
|
|
97
|
+
<Text weight="bold" fontSize="2xl">
|
|
98
|
+
{testimonials[currentIndex]?.name}
|
|
99
|
+
</Text>
|
|
100
|
+
<Text muted>{testimonials[currentIndex]?.jobTitle}</Text>
|
|
101
|
+
</div>
|
|
102
|
+
</Card>
|
|
103
|
+
)}
|
|
104
|
+
{testimonials && testimonials?.length > 1 && (
|
|
105
|
+
<SwiperButton
|
|
106
|
+
type="right"
|
|
107
|
+
className="hidden bg-white lg:block p-4 mr-6 lg:mr-0"
|
|
108
|
+
onClick={() => handleSlider("next")}
|
|
109
|
+
ariaLabel="Show next testimonial"
|
|
110
|
+
/>
|
|
111
|
+
)}
|
|
112
|
+
</div>
|
|
113
|
+
</Container>
|
|
114
|
+
</Section>
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function RatingIcon() {
|
|
119
|
+
return (
|
|
120
|
+
<svg
|
|
121
|
+
className="w-6 h-6"
|
|
122
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
123
|
+
viewBox="0 0 20 20"
|
|
124
|
+
fill="currentColor">
|
|
125
|
+
<path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" />
|
|
126
|
+
</svg>
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function QuoteIcon() {
|
|
131
|
+
return (
|
|
132
|
+
<svg
|
|
133
|
+
className="w-10 h-10 mb-4 text-primary"
|
|
134
|
+
viewBox="0 0 32 28"
|
|
135
|
+
fill="none"
|
|
136
|
+
xmlns="http://www.w3.org/2000/svg">
|
|
137
|
+
<path
|
|
138
|
+
d="M10.2418 12.749C9.45369 12.522 8.66554 12.4069 7.89887 12.4069C6.71496 12.4069 5.72709 12.6775 4.96109 13.0088C5.69957 10.3053 7.47358 5.6405 11.0075 5.11517C11.3348 5.0665 11.603 4.82986 11.6923 4.51131L12.4646 1.74875C12.5298 1.51512 12.4912 1.26505 12.3579 1.06231C12.2246 0.859563 12.0105 0.724288 11.7705 0.691393C11.5097 0.655812 11.2438 0.637686 10.9803 0.637686C6.73846 0.637686 2.53756 5.06516 0.764895 11.4046C-0.275679 15.1238 -0.580802 20.7154 1.98237 24.2349C3.41668 26.2043 5.50924 27.2559 8.20198 27.361C8.21305 27.3613 8.2238 27.3616 8.23487 27.3616C11.5573 27.3616 14.5035 25.1241 15.3997 21.9208C15.9351 20.0058 15.6931 17.9975 14.7176 16.2644C13.7526 14.5508 12.1632 13.3018 10.2418 12.749Z"
|
|
139
|
+
fill="currentColor"
|
|
140
|
+
/>
|
|
141
|
+
<path
|
|
142
|
+
d="M31.0396 16.2648C30.0746 14.5508 28.4852 13.3018 26.5638 12.749C25.7757 12.522 24.9875 12.4069 24.2212 12.4069C23.0373 12.4069 22.0491 12.6775 21.2831 13.0088C22.0215 10.3053 23.7955 5.6405 27.3298 5.11517C27.6571 5.0665 27.9249 4.82986 28.0146 4.51131L28.7869 1.74875C28.8521 1.51512 28.8135 1.26505 28.6802 1.06231C28.5473 0.859563 28.3331 0.724288 28.0928 0.691393C27.8323 0.655812 27.5664 0.637686 27.3026 0.637686C23.0608 0.637686 18.8599 5.06516 17.0869 11.4046C16.0466 15.1238 15.7415 20.7154 18.305 24.2356C19.739 26.2046 21.8319 27.2566 24.5243 27.3613C24.5354 27.3616 24.5461 27.362 24.5575 27.362C27.8796 27.362 30.8261 25.1244 31.7224 21.9211C32.2571 20.0061 32.0147 17.9975 31.0396 16.2648Z"
|
|
143
|
+
fill="currentColor"
|
|
144
|
+
/>
|
|
145
|
+
</svg>
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export { Testimonial_D };
|