@windstream/react-shared-components 0.2.35 → 0.2.37

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.
@@ -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: any, index: number) => {
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
- <div className="md:hidden">
196
- {mobileSidebarTabType ? tabViewJSX : accordionViewJSX}
197
- </div>
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
- <div className="hidden md:block">
200
- {navigationType ? tabViewJSX : sidebarViewJSX}
201
- </div>
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"
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 React from "react";
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
- tabsContent?: React.ReactNode[];
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;