@windstream/react-shared-components 0.2.35 → 0.2.36
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/contentful/index.d.ts +22 -11
- package/dist/contentful/index.esm.js +3 -3
- package/dist/contentful/index.esm.js.map +1 -1
- package/dist/contentful/index.js +3 -3
- package/dist/contentful/index.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/styles.css +1 -1
- package/package.json +1 -1
- package/src/contentful/blocks/dynamic-tabs/index.test.tsx +96 -8
- package/src/contentful/blocks/dynamic-tabs/index.tsx +110 -134
- package/src/contentful/blocks/dynamic-tabs/ordered-tab-views.tsx +239 -0
- package/src/contentful/blocks/dynamic-tabs/tab-views.tsx +147 -0
- package/src/contentful/blocks/dynamic-tabs/types.ts +11 -7
- package/src/contentful/blocks/ordered-list/index.tsx +100 -0
- package/src/contentful/blocks/ordered-list/types.ts +9 -0
- package/src/contentful/index.ts +2 -0
|
@@ -1,27 +1,32 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import React, { useEffect, useState } from "react";
|
|
4
|
+
import {
|
|
5
|
+
OrderedAccordionView,
|
|
6
|
+
OrderedSidebarView,
|
|
7
|
+
OrderedTabView,
|
|
8
|
+
} from "./ordered-tab-views";
|
|
9
|
+
import { AccordionView, SidebarView, TabView } from "./tab-views";
|
|
4
10
|
import { DynamicTabsT } from "./types";
|
|
5
|
-
import { AnimatePresence, motion } from "framer-motion";
|
|
6
11
|
|
|
7
|
-
import { Collapse } from "@shared/components/collapse";
|
|
8
|
-
import { MaterialIcon } from "@shared/components/material-icon";
|
|
9
12
|
import { Text } from "@shared/components/text";
|
|
10
|
-
import { Button } from "@shared/contentful/blocks/button";
|
|
11
|
-
import { cx } from "@shared/utils";
|
|
12
13
|
|
|
13
14
|
export function DynamicTabs(props: DynamicTabsT) {
|
|
14
15
|
const [currentTabIndex, setCurrentTabIndex] = useState(0);
|
|
15
16
|
const [fragment, setFragment] = useState("");
|
|
17
|
+
const [subitemColCount, setSubitemColCount] = useState<number>(2);
|
|
16
18
|
const {
|
|
17
19
|
title,
|
|
18
20
|
subTitle,
|
|
19
21
|
tabs,
|
|
20
|
-
tabsContent,
|
|
21
22
|
navigationType = true,
|
|
22
23
|
mobileSidebarTabType = true,
|
|
24
|
+
dynamic = false,
|
|
25
|
+
isOrderedList = false,
|
|
23
26
|
} = props;
|
|
24
27
|
|
|
28
|
+
const items = tabs?.items ?? [];
|
|
29
|
+
|
|
25
30
|
useEffect(() => {
|
|
26
31
|
if (typeof window !== "undefined") {
|
|
27
32
|
setFragment(window.location.hash);
|
|
@@ -37,7 +42,7 @@ export function DynamicTabs(props: DynamicTabsT) {
|
|
|
37
42
|
}, []);
|
|
38
43
|
|
|
39
44
|
useEffect(() => {
|
|
40
|
-
tabs?.items?.some((tab
|
|
45
|
+
tabs?.items?.some((tab, index) => {
|
|
41
46
|
const tabId = `#${tab?.anchorId || "tab-" + index}`;
|
|
42
47
|
if (tabId === fragment) {
|
|
43
48
|
setCurrentTabIndex(index);
|
|
@@ -46,6 +51,31 @@ export function DynamicTabs(props: DynamicTabsT) {
|
|
|
46
51
|
});
|
|
47
52
|
}, [tabs, fragment]);
|
|
48
53
|
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
if (!dynamic) return;
|
|
56
|
+
|
|
57
|
+
const updateSubitemColCount = () => {
|
|
58
|
+
if (typeof window === "undefined") return;
|
|
59
|
+
const width = window.innerWidth;
|
|
60
|
+
if (width <= 640) {
|
|
61
|
+
setSubitemColCount(2); // Small devices
|
|
62
|
+
} else if (width > 750 || width <= 768) {
|
|
63
|
+
setSubitemColCount(3); // Medium devices
|
|
64
|
+
} else if (width <= 1024) {
|
|
65
|
+
setSubitemColCount(2); // Large devices
|
|
66
|
+
} else {
|
|
67
|
+
setSubitemColCount(3); // Large devices
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
updateSubitemColCount();
|
|
72
|
+
window.addEventListener("resize", updateSubitemColCount);
|
|
73
|
+
|
|
74
|
+
return () => {
|
|
75
|
+
window.removeEventListener("resize", updateSubitemColCount);
|
|
76
|
+
};
|
|
77
|
+
}, [dynamic]);
|
|
78
|
+
|
|
49
79
|
const handleTabClick = (index: number, anchor: string) => {
|
|
50
80
|
setCurrentTabIndex(index);
|
|
51
81
|
|
|
@@ -54,127 +84,6 @@ export function DynamicTabs(props: DynamicTabsT) {
|
|
|
54
84
|
}
|
|
55
85
|
};
|
|
56
86
|
|
|
57
|
-
const tabViewJSX = (
|
|
58
|
-
<>
|
|
59
|
-
<div className="overflow-x-auto px-5 [-ms-overflow-style:none] [scrollbar-width:none] md:flex md:justify-center [&::-webkit-scrollbar]:hidden">
|
|
60
|
-
<div className="flex w-max gap-10">
|
|
61
|
-
{tabs?.items?.map((item: any, index: number) => (
|
|
62
|
-
<Button
|
|
63
|
-
showButtonAs="unstyled"
|
|
64
|
-
key={index}
|
|
65
|
-
onClick={() => handleTabClick(index, item?.anchorId)}
|
|
66
|
-
buttonClassName={cx(
|
|
67
|
-
"py-2 whitespace-nowrap",
|
|
68
|
-
index === currentTabIndex
|
|
69
|
-
? "label1 border-b-2 border-border-focus-secondary"
|
|
70
|
-
: "body1"
|
|
71
|
-
)}
|
|
72
|
-
>
|
|
73
|
-
{item.tabName}
|
|
74
|
-
</Button>
|
|
75
|
-
))}
|
|
76
|
-
</div>
|
|
77
|
-
</div>
|
|
78
|
-
|
|
79
|
-
{tabsContent?.map((tab: any, index: number) => {
|
|
80
|
-
return (
|
|
81
|
-
<div key={index}>
|
|
82
|
-
<AnimatePresence>
|
|
83
|
-
<motion.div
|
|
84
|
-
initial={{ height: 0, opacity: 0 }}
|
|
85
|
-
animate={{
|
|
86
|
-
height: index === currentTabIndex ? "auto" : 0,
|
|
87
|
-
opacity: index === currentTabIndex ? 1 : 0,
|
|
88
|
-
}}
|
|
89
|
-
exit={{ height: 0, opacity: 0 }}
|
|
90
|
-
transition={{ duration: 0.3 }}
|
|
91
|
-
>
|
|
92
|
-
{tab}
|
|
93
|
-
</motion.div>
|
|
94
|
-
</AnimatePresence>
|
|
95
|
-
</div>
|
|
96
|
-
);
|
|
97
|
-
})}
|
|
98
|
-
</>
|
|
99
|
-
);
|
|
100
|
-
|
|
101
|
-
const accordionViewJSX = (
|
|
102
|
-
<>
|
|
103
|
-
{tabs?.items?.map((item: any, index: number) => (
|
|
104
|
-
<div key={index} className="py-5">
|
|
105
|
-
<Button
|
|
106
|
-
showButtonAs="unstyled"
|
|
107
|
-
onClick={() => handleTabClick(index, item?.anchorId)}
|
|
108
|
-
buttonClassName={cx(
|
|
109
|
-
"w-full flex justify-between items-center px-4 py-5",
|
|
110
|
-
index === currentTabIndex ? "bg-bg-surface-active" : ""
|
|
111
|
-
)}
|
|
112
|
-
>
|
|
113
|
-
<span className={"heading6"}>{item.tabName}</span>
|
|
114
|
-
<MaterialIcon
|
|
115
|
-
name={
|
|
116
|
-
index === currentTabIndex
|
|
117
|
-
? "keyboard_arrow_down"
|
|
118
|
-
: "keyboard_arrow_up"
|
|
119
|
-
}
|
|
120
|
-
fill={1}
|
|
121
|
-
size={48}
|
|
122
|
-
/>
|
|
123
|
-
</Button>
|
|
124
|
-
<Collapse open={index === currentTabIndex}>
|
|
125
|
-
<div className={cx("px-4 py-4")}>{tabsContent?.[index]}</div>
|
|
126
|
-
</Collapse>
|
|
127
|
-
</div>
|
|
128
|
-
))}
|
|
129
|
-
</>
|
|
130
|
-
);
|
|
131
|
-
|
|
132
|
-
const sidebarViewJSX = (
|
|
133
|
-
<div className="flex px-5 py-20">
|
|
134
|
-
<div className="rounded-surface-lg border border-border-secondary shadow-lg">
|
|
135
|
-
<div className="flex min-w-80 flex-col gap-4 px-6 py-7">
|
|
136
|
-
{tabs?.items?.map((item: any, index: number) => (
|
|
137
|
-
<Button
|
|
138
|
-
showButtonAs="unstyled"
|
|
139
|
-
key={index}
|
|
140
|
-
onClick={() => handleTabClick(index, item?.anchorId)}
|
|
141
|
-
buttonClassName={cx(
|
|
142
|
-
"px-4 py-3 whitespace-nowrap text-left rounded-button-sm",
|
|
143
|
-
index === currentTabIndex
|
|
144
|
-
? "label1 text-white bg-bg-inverse"
|
|
145
|
-
: "body1"
|
|
146
|
-
)}
|
|
147
|
-
>
|
|
148
|
-
{item.tabName}
|
|
149
|
-
</Button>
|
|
150
|
-
))}
|
|
151
|
-
</div>
|
|
152
|
-
</div>
|
|
153
|
-
|
|
154
|
-
<div className="grow">
|
|
155
|
-
{tabsContent?.map((tab: any, index: number) => {
|
|
156
|
-
return (
|
|
157
|
-
<div key={index}>
|
|
158
|
-
<AnimatePresence>
|
|
159
|
-
<motion.div
|
|
160
|
-
initial={{ height: 0, opacity: 0 }}
|
|
161
|
-
animate={{
|
|
162
|
-
height: index === currentTabIndex ? "auto" : 0,
|
|
163
|
-
opacity: index === currentTabIndex ? 1 : 0,
|
|
164
|
-
}}
|
|
165
|
-
exit={{ height: 0, opacity: 0 }}
|
|
166
|
-
transition={{ duration: 0.3 }}
|
|
167
|
-
>
|
|
168
|
-
{tab}
|
|
169
|
-
</motion.div>
|
|
170
|
-
</AnimatePresence>
|
|
171
|
-
</div>
|
|
172
|
-
);
|
|
173
|
-
})}
|
|
174
|
-
</div>
|
|
175
|
-
</div>
|
|
176
|
-
);
|
|
177
|
-
|
|
178
87
|
return (
|
|
179
88
|
<div>
|
|
180
89
|
{title || subTitle ? (
|
|
@@ -192,13 +101,80 @@ export function DynamicTabs(props: DynamicTabsT) {
|
|
|
192
101
|
</div>
|
|
193
102
|
) : null}
|
|
194
103
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
104
|
+
{dynamic && isOrderedList ? (
|
|
105
|
+
<>
|
|
106
|
+
<div className="md:hidden">
|
|
107
|
+
{mobileSidebarTabType ? (
|
|
108
|
+
<OrderedTabView
|
|
109
|
+
items={items}
|
|
110
|
+
currentTabIndex={currentTabIndex}
|
|
111
|
+
subitemColCount={subitemColCount}
|
|
112
|
+
onTabClick={handleTabClick}
|
|
113
|
+
/>
|
|
114
|
+
) : (
|
|
115
|
+
<OrderedAccordionView
|
|
116
|
+
items={items}
|
|
117
|
+
currentTabIndex={currentTabIndex}
|
|
118
|
+
subitemColCount={subitemColCount}
|
|
119
|
+
onTabClick={handleTabClick}
|
|
120
|
+
setCurrentTabIndex={setCurrentTabIndex}
|
|
121
|
+
/>
|
|
122
|
+
)}
|
|
123
|
+
</div>
|
|
198
124
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
125
|
+
<div className="hidden md:block">
|
|
126
|
+
{navigationType ? (
|
|
127
|
+
<OrderedTabView
|
|
128
|
+
items={items}
|
|
129
|
+
currentTabIndex={currentTabIndex}
|
|
130
|
+
subitemColCount={subitemColCount}
|
|
131
|
+
onTabClick={handleTabClick}
|
|
132
|
+
/>
|
|
133
|
+
) : (
|
|
134
|
+
<OrderedSidebarView
|
|
135
|
+
items={items}
|
|
136
|
+
currentTabIndex={currentTabIndex}
|
|
137
|
+
subitemColCount={subitemColCount}
|
|
138
|
+
onTabClick={handleTabClick}
|
|
139
|
+
/>
|
|
140
|
+
)}
|
|
141
|
+
</div>
|
|
142
|
+
</>
|
|
143
|
+
) : (
|
|
144
|
+
<>
|
|
145
|
+
<div className="md:hidden">
|
|
146
|
+
{mobileSidebarTabType ? (
|
|
147
|
+
<TabView
|
|
148
|
+
items={items}
|
|
149
|
+
currentTabIndex={currentTabIndex}
|
|
150
|
+
onTabClick={handleTabClick}
|
|
151
|
+
/>
|
|
152
|
+
) : (
|
|
153
|
+
<AccordionView
|
|
154
|
+
items={items}
|
|
155
|
+
currentTabIndex={currentTabIndex}
|
|
156
|
+
onTabClick={handleTabClick}
|
|
157
|
+
/>
|
|
158
|
+
)}
|
|
159
|
+
</div>
|
|
160
|
+
|
|
161
|
+
<div className="hidden md:block">
|
|
162
|
+
{navigationType ? (
|
|
163
|
+
<TabView
|
|
164
|
+
items={items}
|
|
165
|
+
currentTabIndex={currentTabIndex}
|
|
166
|
+
onTabClick={handleTabClick}
|
|
167
|
+
/>
|
|
168
|
+
) : (
|
|
169
|
+
<SidebarView
|
|
170
|
+
items={items}
|
|
171
|
+
currentTabIndex={currentTabIndex}
|
|
172
|
+
onTabClick={handleTabClick}
|
|
173
|
+
/>
|
|
174
|
+
)}
|
|
175
|
+
</div>
|
|
176
|
+
</>
|
|
177
|
+
)}
|
|
202
178
|
</div>
|
|
203
179
|
);
|
|
204
180
|
}
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import React, { ReactNode } from "react";
|
|
4
|
+
import { DynamicTabT } from "./types";
|
|
5
|
+
import { AnimatePresence, motion } from "framer-motion";
|
|
6
|
+
|
|
7
|
+
import { MaterialIcon } from "@shared/components/material-icon";
|
|
8
|
+
import { Button } from "@shared/contentful/blocks/button";
|
|
9
|
+
import { cx } from "@shared/utils";
|
|
10
|
+
|
|
11
|
+
export interface OrderedTabViewProps {
|
|
12
|
+
items: DynamicTabT[];
|
|
13
|
+
currentTabIndex: number;
|
|
14
|
+
subitemColCount: number;
|
|
15
|
+
onTabClick: (index: number, anchor: string) => void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface OrderedAccordionViewProps extends OrderedTabViewProps {
|
|
19
|
+
setCurrentTabIndex: (index: number) => void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Renders a tab's `tabContent` nodes arranged into `subitemColCount` columns.
|
|
23
|
+
export function renderOrderedChildren(
|
|
24
|
+
content: ReactNode,
|
|
25
|
+
subitemColCount: number
|
|
26
|
+
) {
|
|
27
|
+
const children = Array.isArray(content) ? content : [content];
|
|
28
|
+
const colCount = Math.min(subitemColCount, children.length);
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<div className="flex flex-col py-7">
|
|
32
|
+
<div className="flex shrink-0 basis-0 flex-row gap-16 md:gap-20">
|
|
33
|
+
{[...new Array(colCount)].map((_, colIdx) => (
|
|
34
|
+
<ul
|
|
35
|
+
key={`ordered-col-${colIdx}`}
|
|
36
|
+
className="flex flex-col gap-5"
|
|
37
|
+
style={{ width: "172px" }}
|
|
38
|
+
>
|
|
39
|
+
{children
|
|
40
|
+
.slice(
|
|
41
|
+
colIdx * Math.ceil(children.length / colCount),
|
|
42
|
+
(colIdx + 1) * Math.ceil(children.length / colCount)
|
|
43
|
+
)
|
|
44
|
+
.map((child, rowIdx) => (
|
|
45
|
+
<li key={`ordered-item-${colIdx}-${rowIdx}`}>{child}</li>
|
|
46
|
+
))}
|
|
47
|
+
</ul>
|
|
48
|
+
))}
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function OrderedTabView({
|
|
55
|
+
items,
|
|
56
|
+
currentTabIndex,
|
|
57
|
+
subitemColCount,
|
|
58
|
+
onTabClick,
|
|
59
|
+
}: OrderedTabViewProps) {
|
|
60
|
+
return (
|
|
61
|
+
<>
|
|
62
|
+
<div className="overflow-x-auto px-5 [-ms-overflow-style:none] [scrollbar-width:none] md:flex md:justify-center [&::-webkit-scrollbar]:hidden">
|
|
63
|
+
<div className="flex w-max gap-10">
|
|
64
|
+
{items.map((tab, index) => (
|
|
65
|
+
<Button
|
|
66
|
+
showButtonAs="unstyled"
|
|
67
|
+
key={index}
|
|
68
|
+
onClick={() => onTabClick(index, tab.anchorId || "")}
|
|
69
|
+
buttonClassName={cx(
|
|
70
|
+
"py-2 whitespace-nowrap",
|
|
71
|
+
index === currentTabIndex
|
|
72
|
+
? "label1 border-b-2 border-border-focus-secondary"
|
|
73
|
+
: "body1"
|
|
74
|
+
)}
|
|
75
|
+
>
|
|
76
|
+
{tab.tabName}
|
|
77
|
+
</Button>
|
|
78
|
+
))}
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
|
|
82
|
+
{items.map((tab, index) => (
|
|
83
|
+
<div key={index}>
|
|
84
|
+
<AnimatePresence>
|
|
85
|
+
<motion.div
|
|
86
|
+
initial={{ height: 0, opacity: 0 }}
|
|
87
|
+
animate={{
|
|
88
|
+
height: index === currentTabIndex ? "auto" : 0,
|
|
89
|
+
opacity: index === currentTabIndex ? 1 : 0,
|
|
90
|
+
}}
|
|
91
|
+
exit={{ height: 0, opacity: 0 }}
|
|
92
|
+
transition={{ duration: 0.3 }}
|
|
93
|
+
className={cx(
|
|
94
|
+
"overflow-hidden",
|
|
95
|
+
index === currentTabIndex ? "block" : "hidden"
|
|
96
|
+
)}
|
|
97
|
+
id={tab.anchorId || `tab-${index}`}
|
|
98
|
+
>
|
|
99
|
+
<div className="px-5 py-4 md:px-10">
|
|
100
|
+
{renderOrderedChildren(tab.tabContent, subitemColCount)}
|
|
101
|
+
</div>
|
|
102
|
+
</motion.div>
|
|
103
|
+
</AnimatePresence>
|
|
104
|
+
</div>
|
|
105
|
+
))}
|
|
106
|
+
</>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function OrderedAccordionView({
|
|
111
|
+
items,
|
|
112
|
+
currentTabIndex,
|
|
113
|
+
subitemColCount,
|
|
114
|
+
onTabClick,
|
|
115
|
+
setCurrentTabIndex,
|
|
116
|
+
}: OrderedAccordionViewProps) {
|
|
117
|
+
return (
|
|
118
|
+
<div className="flex w-full flex-col">
|
|
119
|
+
{items.map((tab, index) => (
|
|
120
|
+
<div key={index}>
|
|
121
|
+
<button
|
|
122
|
+
onClick={() =>
|
|
123
|
+
index === currentTabIndex
|
|
124
|
+
? setCurrentTabIndex(-1)
|
|
125
|
+
: onTabClick(index, tab.anchorId || "")
|
|
126
|
+
}
|
|
127
|
+
className={cx(
|
|
128
|
+
"flex w-full items-center justify-between px-5 py-4 text-text md:px-10",
|
|
129
|
+
index === currentTabIndex ? "bg-bg-surface-hover" : ""
|
|
130
|
+
)}
|
|
131
|
+
>
|
|
132
|
+
<span className="heading6">{tab.tabName}</span>
|
|
133
|
+
<MaterialIcon
|
|
134
|
+
name={
|
|
135
|
+
index === currentTabIndex
|
|
136
|
+
? "keyboard_arrow_up"
|
|
137
|
+
: "keyboard_arrow_down"
|
|
138
|
+
}
|
|
139
|
+
className="text-text"
|
|
140
|
+
size={40}
|
|
141
|
+
/>
|
|
142
|
+
</button>
|
|
143
|
+
<AnimatePresence>
|
|
144
|
+
<motion.div
|
|
145
|
+
initial={{ height: 0, opacity: 0 }}
|
|
146
|
+
animate={{
|
|
147
|
+
height: index === currentTabIndex ? "auto" : 0,
|
|
148
|
+
opacity: index === currentTabIndex ? 1 : 0,
|
|
149
|
+
}}
|
|
150
|
+
exit={{ height: 0, opacity: 0 }}
|
|
151
|
+
transition={{ duration: 0.3 }}
|
|
152
|
+
className={cx(
|
|
153
|
+
"overflow-hidden",
|
|
154
|
+
index === currentTabIndex ? "block" : "hidden"
|
|
155
|
+
)}
|
|
156
|
+
id={tab.anchorId || `tab-${index}`}
|
|
157
|
+
>
|
|
158
|
+
<div className="px-5 py-4 md:px-10">
|
|
159
|
+
{renderOrderedChildren(tab.tabContent, subitemColCount)}
|
|
160
|
+
</div>
|
|
161
|
+
</motion.div>
|
|
162
|
+
</AnimatePresence>
|
|
163
|
+
</div>
|
|
164
|
+
))}
|
|
165
|
+
</div>
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export function OrderedSidebarView({
|
|
170
|
+
items,
|
|
171
|
+
currentTabIndex,
|
|
172
|
+
subitemColCount,
|
|
173
|
+
onTabClick,
|
|
174
|
+
}: OrderedTabViewProps) {
|
|
175
|
+
return (
|
|
176
|
+
<div className="flex gap-10 px-5 py-20">
|
|
177
|
+
<div className="flex-shrink-0">
|
|
178
|
+
<div className="flex min-w-[320px] gap-4 rounded-[20px] border-2 bg-bg-surface-active px-6 py-7 shadow-lg">
|
|
179
|
+
{[...new Array(Math.min(2, items.length))].map((_, colIdx) => {
|
|
180
|
+
const perColumn = Math.ceil(items.length / 2);
|
|
181
|
+
|
|
182
|
+
return (
|
|
183
|
+
<div
|
|
184
|
+
key={`tab-col-${colIdx}`}
|
|
185
|
+
className="flex flex-1 flex-col gap-4"
|
|
186
|
+
>
|
|
187
|
+
{items
|
|
188
|
+
.slice(colIdx * perColumn, (colIdx + 1) * perColumn)
|
|
189
|
+
.map((tab, rowIdx) => {
|
|
190
|
+
const index = colIdx * perColumn + rowIdx;
|
|
191
|
+
|
|
192
|
+
return (
|
|
193
|
+
<button
|
|
194
|
+
key={index}
|
|
195
|
+
onClick={() => onTabClick(index, tab.anchorId || "")}
|
|
196
|
+
className={cx(
|
|
197
|
+
"label1 whitespace-nowrap rounded-lg px-4 py-3 text-left",
|
|
198
|
+
index === currentTabIndex
|
|
199
|
+
? "bg-bg-inverse text-white"
|
|
200
|
+
: "text-text hover:bg-bg-inverse"
|
|
201
|
+
)}
|
|
202
|
+
>
|
|
203
|
+
{tab.tabName}
|
|
204
|
+
</button>
|
|
205
|
+
);
|
|
206
|
+
})}
|
|
207
|
+
</div>
|
|
208
|
+
);
|
|
209
|
+
})}
|
|
210
|
+
</div>
|
|
211
|
+
</div>
|
|
212
|
+
|
|
213
|
+
<div className="w-full">
|
|
214
|
+
{items.map((tab, index) => (
|
|
215
|
+
<div key={index}>
|
|
216
|
+
<AnimatePresence>
|
|
217
|
+
<motion.div
|
|
218
|
+
initial={{ opacity: 0, y: 20 }}
|
|
219
|
+
animate={{
|
|
220
|
+
opacity: index === currentTabIndex ? 1 : 0,
|
|
221
|
+
y: index === currentTabIndex ? 0 : 20,
|
|
222
|
+
}}
|
|
223
|
+
exit={{ opacity: 0, y: -20 }}
|
|
224
|
+
transition={{ duration: 0.3 }}
|
|
225
|
+
className={cx(
|
|
226
|
+
"overflow-hidden pl-10 xl:pl-20",
|
|
227
|
+
index === currentTabIndex ? "block" : "hidden"
|
|
228
|
+
)}
|
|
229
|
+
id={tab.anchorId || `tab-${index}`}
|
|
230
|
+
>
|
|
231
|
+
{renderOrderedChildren(tab.tabContent, subitemColCount)}
|
|
232
|
+
</motion.div>
|
|
233
|
+
</AnimatePresence>
|
|
234
|
+
</div>
|
|
235
|
+
))}
|
|
236
|
+
</div>
|
|
237
|
+
</div>
|
|
238
|
+
);
|
|
239
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import React from "react";
|
|
4
|
+
import { DynamicTabT } from "./types";
|
|
5
|
+
import { AnimatePresence, motion } from "framer-motion";
|
|
6
|
+
|
|
7
|
+
import { Collapse } from "@shared/components/collapse";
|
|
8
|
+
import { MaterialIcon } from "@shared/components/material-icon";
|
|
9
|
+
import { Button } from "@shared/contentful/blocks/button";
|
|
10
|
+
import { cx } from "@shared/utils";
|
|
11
|
+
|
|
12
|
+
export interface TabViewProps {
|
|
13
|
+
items: DynamicTabT[];
|
|
14
|
+
currentTabIndex: number;
|
|
15
|
+
onTabClick: (index: number, anchor: string) => void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function TabView({ items, currentTabIndex, onTabClick }: TabViewProps) {
|
|
19
|
+
return (
|
|
20
|
+
<>
|
|
21
|
+
<div className="overflow-x-auto px-5 [-ms-overflow-style:none] [scrollbar-width:none] md:flex md:justify-center [&::-webkit-scrollbar]:hidden">
|
|
22
|
+
<div className="flex w-max gap-10">
|
|
23
|
+
{items.map((item, index) => (
|
|
24
|
+
<Button
|
|
25
|
+
showButtonAs="unstyled"
|
|
26
|
+
key={index}
|
|
27
|
+
onClick={() => onTabClick(index, item.anchorId)}
|
|
28
|
+
buttonClassName={cx(
|
|
29
|
+
"py-2 whitespace-nowrap",
|
|
30
|
+
index === currentTabIndex
|
|
31
|
+
? "label1 border-b-2 border-border-focus-secondary"
|
|
32
|
+
: "body1"
|
|
33
|
+
)}
|
|
34
|
+
>
|
|
35
|
+
{item.tabName}
|
|
36
|
+
</Button>
|
|
37
|
+
))}
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
|
|
41
|
+
{items.map((item, index) => (
|
|
42
|
+
<div key={index}>
|
|
43
|
+
<AnimatePresence>
|
|
44
|
+
<motion.div
|
|
45
|
+
initial={{ height: 0, opacity: 0 }}
|
|
46
|
+
animate={{
|
|
47
|
+
height: index === currentTabIndex ? "auto" : 0,
|
|
48
|
+
opacity: index === currentTabIndex ? 1 : 0,
|
|
49
|
+
}}
|
|
50
|
+
exit={{ height: 0, opacity: 0 }}
|
|
51
|
+
transition={{ duration: 0.3 }}
|
|
52
|
+
>
|
|
53
|
+
{item.tabContent}
|
|
54
|
+
</motion.div>
|
|
55
|
+
</AnimatePresence>
|
|
56
|
+
</div>
|
|
57
|
+
))}
|
|
58
|
+
</>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function AccordionView({
|
|
63
|
+
items,
|
|
64
|
+
currentTabIndex,
|
|
65
|
+
onTabClick,
|
|
66
|
+
}: TabViewProps) {
|
|
67
|
+
return (
|
|
68
|
+
<>
|
|
69
|
+
{items.map((item, index) => (
|
|
70
|
+
<div key={index} className="py-5">
|
|
71
|
+
<Button
|
|
72
|
+
showButtonAs="unstyled"
|
|
73
|
+
onClick={() => onTabClick(index, item.anchorId)}
|
|
74
|
+
buttonClassName={cx(
|
|
75
|
+
"w-full flex justify-between items-center px-4 py-5",
|
|
76
|
+
index === currentTabIndex ? "bg-bg-surface-active" : ""
|
|
77
|
+
)}
|
|
78
|
+
>
|
|
79
|
+
<span className={"heading6"}>{item.tabName}</span>
|
|
80
|
+
<MaterialIcon
|
|
81
|
+
name={
|
|
82
|
+
index === currentTabIndex
|
|
83
|
+
? "keyboard_arrow_down"
|
|
84
|
+
: "keyboard_arrow_up"
|
|
85
|
+
}
|
|
86
|
+
fill={1}
|
|
87
|
+
size={48}
|
|
88
|
+
/>
|
|
89
|
+
</Button>
|
|
90
|
+
<Collapse open={index === currentTabIndex}>
|
|
91
|
+
<div className={cx("px-4 py-4")}>{item.tabContent}</div>
|
|
92
|
+
</Collapse>
|
|
93
|
+
</div>
|
|
94
|
+
))}
|
|
95
|
+
</>
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function SidebarView({
|
|
100
|
+
items,
|
|
101
|
+
currentTabIndex,
|
|
102
|
+
onTabClick,
|
|
103
|
+
}: TabViewProps) {
|
|
104
|
+
return (
|
|
105
|
+
<div className="flex px-5 py-20">
|
|
106
|
+
<div className="rounded-surface-lg border border-border-secondary shadow-lg">
|
|
107
|
+
<div className="flex min-w-80 flex-col gap-4 px-6 py-7">
|
|
108
|
+
{items.map((item, index) => (
|
|
109
|
+
<Button
|
|
110
|
+
showButtonAs="unstyled"
|
|
111
|
+
key={index}
|
|
112
|
+
onClick={() => onTabClick(index, item.anchorId)}
|
|
113
|
+
buttonClassName={cx(
|
|
114
|
+
"px-4 py-3 whitespace-nowrap text-left rounded-button-sm",
|
|
115
|
+
index === currentTabIndex
|
|
116
|
+
? "label1 text-white bg-bg-inverse"
|
|
117
|
+
: "body1"
|
|
118
|
+
)}
|
|
119
|
+
>
|
|
120
|
+
{item.tabName}
|
|
121
|
+
</Button>
|
|
122
|
+
))}
|
|
123
|
+
</div>
|
|
124
|
+
</div>
|
|
125
|
+
|
|
126
|
+
<div className="grow">
|
|
127
|
+
{items.map((item, index) => (
|
|
128
|
+
<div key={index}>
|
|
129
|
+
<AnimatePresence>
|
|
130
|
+
<motion.div
|
|
131
|
+
initial={{ height: 0, opacity: 0 }}
|
|
132
|
+
animate={{
|
|
133
|
+
height: index === currentTabIndex ? "auto" : 0,
|
|
134
|
+
opacity: index === currentTabIndex ? 1 : 0,
|
|
135
|
+
}}
|
|
136
|
+
exit={{ height: 0, opacity: 0 }}
|
|
137
|
+
transition={{ duration: 0.3 }}
|
|
138
|
+
>
|
|
139
|
+
{item.tabContent}
|
|
140
|
+
</motion.div>
|
|
141
|
+
</AnimatePresence>
|
|
142
|
+
</div>
|
|
143
|
+
))}
|
|
144
|
+
</div>
|
|
145
|
+
</div>
|
|
146
|
+
);
|
|
147
|
+
}
|
|
@@ -1,14 +1,18 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
export interface DynamicTabT {
|
|
4
|
+
tabName: string;
|
|
5
|
+
anchorId: string;
|
|
6
|
+
tabContent: ReactNode;
|
|
7
|
+
}
|
|
2
8
|
|
|
3
9
|
export interface DynamicTabsT {
|
|
4
10
|
tabs?: {
|
|
5
|
-
items:
|
|
6
|
-
tabName: string;
|
|
7
|
-
anchorId: string;
|
|
8
|
-
tabContent: { items: any[] };
|
|
9
|
-
}[];
|
|
11
|
+
items: DynamicTabT[];
|
|
10
12
|
};
|
|
11
|
-
|
|
13
|
+
dynamic?: boolean;
|
|
14
|
+
/** When isOrderedList is true, each tab's `tabContent` is rendered as columns of children. */
|
|
15
|
+
isOrderedList?: boolean;
|
|
12
16
|
entryTitle?: string;
|
|
13
17
|
anchorId?: string;
|
|
14
18
|
title?: string;
|