@pradip1995/segment-store-faqs 0.2.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/package.json +38 -0
- package/src/index.ts +2 -0
- package/src/manifest.ts +11 -0
- package/src/segment.css +82 -0
- package/src/segment.tsx +147 -0
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pradip1995/segment-store-faqs",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
8
|
+
"sideEffects": [
|
|
9
|
+
"src/segment.css"
|
|
10
|
+
],
|
|
11
|
+
"files": [
|
|
12
|
+
"src"
|
|
13
|
+
],
|
|
14
|
+
"exports": {
|
|
15
|
+
".": "./src/index.ts",
|
|
16
|
+
"./manifest": "./src/manifest.ts"
|
|
17
|
+
},
|
|
18
|
+
"peerDependencies": {
|
|
19
|
+
"@pradip1995/plugin-sdk": "^0.2.0",
|
|
20
|
+
"react": ">=19",
|
|
21
|
+
"react-dom": ">=19",
|
|
22
|
+
"next": ">=15"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@pradip1995/segment-primitives": "0.3.0",
|
|
26
|
+
"@pradip1995/segment-tokens": "0.3.2"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@pradip1995/plugin-sdk": "^0.2.0",
|
|
30
|
+
"@types/react": "^19",
|
|
31
|
+
"react": "19.0.3",
|
|
32
|
+
"typescript": "^5.7.2"
|
|
33
|
+
},
|
|
34
|
+
"scripts": {
|
|
35
|
+
"typecheck": "tsc --noEmit",
|
|
36
|
+
"lint": "tsc --noEmit"
|
|
37
|
+
}
|
|
38
|
+
}
|
package/src/index.ts
ADDED
package/src/manifest.ts
ADDED
package/src/segment.css
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
.store-faqs {
|
|
2
|
+
width: 100%;
|
|
3
|
+
padding: 2rem 0;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
.store-faqs__inner {
|
|
7
|
+
max-width: var(--container-max);
|
|
8
|
+
margin: 0 auto;
|
|
9
|
+
padding: 2rem 1.5rem;
|
|
10
|
+
background: var(--color-brand-accent-muted);
|
|
11
|
+
border-radius: 1.25rem;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.store-faqs__title {
|
|
15
|
+
font-size: 1.5rem;
|
|
16
|
+
font-weight: 600;
|
|
17
|
+
text-transform: uppercase;
|
|
18
|
+
margin-bottom: 2rem;
|
|
19
|
+
color: var(--color-text-heading);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.store-faqs__category + .store-faqs__category {
|
|
23
|
+
margin-top: 2rem;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.store-faqs__category-title {
|
|
27
|
+
font-size: 1.125rem;
|
|
28
|
+
font-weight: 600;
|
|
29
|
+
margin-bottom: 1rem;
|
|
30
|
+
padding-bottom: 0.5rem;
|
|
31
|
+
border-bottom: 1px solid var(--color-brand-accent);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.store-faqs__question {
|
|
35
|
+
width: 100%;
|
|
36
|
+
display: flex;
|
|
37
|
+
justify-content: space-between;
|
|
38
|
+
gap: 1rem;
|
|
39
|
+
text-align: left;
|
|
40
|
+
background: none;
|
|
41
|
+
border: none;
|
|
42
|
+
padding: 0.75rem 0;
|
|
43
|
+
color: var(--color-text-heading);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.store-faqs__number {
|
|
47
|
+
color: var(--color-brand-accent);
|
|
48
|
+
font-weight: 600;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.store-faqs__toggle {
|
|
52
|
+
color: var(--color-brand-accent);
|
|
53
|
+
font-size: 1.5rem;
|
|
54
|
+
line-height: 1;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.store-faqs__answer {
|
|
58
|
+
margin: 0 0 0.75rem;
|
|
59
|
+
color: var(--color-text-body);
|
|
60
|
+
line-height: 1.6;
|
|
61
|
+
white-space: pre-line;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.store-faqs__divider {
|
|
65
|
+
height: 1px;
|
|
66
|
+
background: var(--color-brand-accent);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.store-faqs__more-wrap {
|
|
70
|
+
display: flex;
|
|
71
|
+
justify-content: center;
|
|
72
|
+
padding-top: 1.5rem;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.store-faqs__more-btn {
|
|
76
|
+
border-radius: 9999px;
|
|
77
|
+
padding: 0.75rem 2rem;
|
|
78
|
+
background: var(--color-brand-accent);
|
|
79
|
+
color: var(--color-text-inverse);
|
|
80
|
+
font-weight: 600;
|
|
81
|
+
text-transform: uppercase;
|
|
82
|
+
}
|
package/src/segment.tsx
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import "./segment.css"
|
|
4
|
+
import { useMemo, useState } from "react"
|
|
5
|
+
|
|
6
|
+
type FaqItem = {
|
|
7
|
+
question: string
|
|
8
|
+
answer: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
type FaqCategory = {
|
|
12
|
+
title: string
|
|
13
|
+
faqs: FaqItem[]
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const DEFAULT_CATEGORIES: FaqCategory[] = [
|
|
17
|
+
{
|
|
18
|
+
title: "Orders & delivery",
|
|
19
|
+
faqs: [
|
|
20
|
+
{
|
|
21
|
+
question: "When will I receive my order?",
|
|
22
|
+
answer: "Orders are typically processed within 1–2 business days. Delivery takes 3–5 business days depending on your location.",
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
question: "How can I track my order?",
|
|
26
|
+
answer: "Once your order ships, you will receive a tracking link via email. You can also track it from your account under My Orders.",
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
title: "Returns & exchanges",
|
|
32
|
+
faqs: [
|
|
33
|
+
{
|
|
34
|
+
question: "What is your return policy?",
|
|
35
|
+
answer: "We offer a 7-day return policy for unused items in original packaging with tags attached.",
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
question: "How do I initiate a return?",
|
|
39
|
+
answer: "Go to your account, open the order, and select Return Items to generate a pickup request.",
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
},
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
export default function StoreFaqs({
|
|
46
|
+
categories,
|
|
47
|
+
title = "FAQs",
|
|
48
|
+
initialVisibleCount = 6,
|
|
49
|
+
}: {
|
|
50
|
+
categories?: FaqCategory[] | null
|
|
51
|
+
title?: string
|
|
52
|
+
initialVisibleCount?: number
|
|
53
|
+
}) {
|
|
54
|
+
const faqCategories = categories && categories.length > 0 ? categories : DEFAULT_CATEGORIES
|
|
55
|
+
const [openIndex, setOpenIndex] = useState<{ category: number; faq: number } | null>(null)
|
|
56
|
+
const [visibleCount, setVisibleCount] = useState(initialVisibleCount)
|
|
57
|
+
|
|
58
|
+
const totalFaqs = useMemo(
|
|
59
|
+
() => faqCategories.reduce((sum, category) => sum + category.faqs.length, 0),
|
|
60
|
+
[faqCategories]
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
const hasMore = totalFaqs > visibleCount
|
|
64
|
+
const showLessButton = visibleCount > initialVisibleCount
|
|
65
|
+
|
|
66
|
+
let globalIndex = 0
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<section className="store-faqs" aria-label={title}>
|
|
70
|
+
<div className="store-faqs__inner">
|
|
71
|
+
<h2 className="store-faqs__title">{title}</h2>
|
|
72
|
+
|
|
73
|
+
<div className="store-faqs__categories">
|
|
74
|
+
{faqCategories.map((category, catIndex) => {
|
|
75
|
+
const firstFaqIndex = globalIndex
|
|
76
|
+
if (firstFaqIndex >= visibleCount) return null
|
|
77
|
+
|
|
78
|
+
const visibleFaqs = category.faqs.filter((_, idx) => firstFaqIndex + idx < visibleCount)
|
|
79
|
+
if (visibleFaqs.length === 0) return null
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
<div key={`${category.title}-${catIndex}`} className="store-faqs__category">
|
|
83
|
+
{category.title ? <h3 className="store-faqs__category-title">{category.title}</h3> : null}
|
|
84
|
+
<div className="store-faqs__list">
|
|
85
|
+
{visibleFaqs.map((faq, faqIndex) => {
|
|
86
|
+
const isExpanded = openIndex?.category === catIndex && openIndex?.faq === faqIndex
|
|
87
|
+
globalIndex += 1
|
|
88
|
+
const displayIndex = globalIndex
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<div key={`${faq.question}-${faqIndex}`} className="store-faqs__item">
|
|
92
|
+
<button
|
|
93
|
+
type="button"
|
|
94
|
+
onClick={() =>
|
|
95
|
+
setOpenIndex(isExpanded ? null : { category: catIndex, faq: faqIndex })
|
|
96
|
+
}
|
|
97
|
+
className="store-faqs__question"
|
|
98
|
+
aria-expanded={isExpanded}
|
|
99
|
+
>
|
|
100
|
+
<span>
|
|
101
|
+
<span className="store-faqs__number">{displayIndex}.</span> {faq.question}
|
|
102
|
+
</span>
|
|
103
|
+
<span className="store-faqs__toggle" aria-hidden>
|
|
104
|
+
{isExpanded ? "−" : "+"}
|
|
105
|
+
</span>
|
|
106
|
+
</button>
|
|
107
|
+
{isExpanded ? (
|
|
108
|
+
<p className="store-faqs__answer">{faq.answer}</p>
|
|
109
|
+
) : null}
|
|
110
|
+
<div className="store-faqs__divider" aria-hidden />
|
|
111
|
+
</div>
|
|
112
|
+
)
|
|
113
|
+
})}
|
|
114
|
+
</div>
|
|
115
|
+
</div>
|
|
116
|
+
)
|
|
117
|
+
})}
|
|
118
|
+
</div>
|
|
119
|
+
|
|
120
|
+
{(hasMore || showLessButton) && (
|
|
121
|
+
<div className="store-faqs__more-wrap">
|
|
122
|
+
{hasMore ? (
|
|
123
|
+
<button
|
|
124
|
+
type="button"
|
|
125
|
+
onClick={() => setVisibleCount((prev) => Math.min(prev + initialVisibleCount, totalFaqs))}
|
|
126
|
+
className="store-faqs__more-btn"
|
|
127
|
+
>
|
|
128
|
+
More
|
|
129
|
+
</button>
|
|
130
|
+
) : (
|
|
131
|
+
<button
|
|
132
|
+
type="button"
|
|
133
|
+
onClick={() => {
|
|
134
|
+
setVisibleCount(initialVisibleCount)
|
|
135
|
+
setOpenIndex(null)
|
|
136
|
+
}}
|
|
137
|
+
className="store-faqs__more-btn"
|
|
138
|
+
>
|
|
139
|
+
Less
|
|
140
|
+
</button>
|
|
141
|
+
)}
|
|
142
|
+
</div>
|
|
143
|
+
)}
|
|
144
|
+
</div>
|
|
145
|
+
</section>
|
|
146
|
+
)
|
|
147
|
+
}
|