@purpurds/tabs 6.12.0 → 6.12.2

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.
@@ -23,9 +23,12 @@
23
23
  color: var(--purpur-color-text-default);
24
24
  background: var(--purpur-color-background-primary);
25
25
  box-shadow: var(--purpur-shadow-md);
26
- transition: all var(--purpur-motion-duration-150) var(--purpur-motion-easing-ease-in-out);
27
26
  cursor: pointer;
28
27
 
28
+ @media (prefers-reduced-motion: no-preference) {
29
+ transition: all var(--purpur-motion-duration-150) var(--purpur-motion-easing-ease-in-out);
30
+ }
31
+
29
32
  &::after {
30
33
  content: "";
31
34
  position: absolute;
@@ -34,7 +37,10 @@
34
37
  border-radius: var(--purpur-border-radius-xs);
35
38
  box-sizing: border-box;
36
39
  opacity: 0;
37
- transition: all var(--purpur-motion-duration-150) var(--purpur-motion-easing-ease-in-out);
40
+
41
+ @media (prefers-reduced-motion: no-preference) {
42
+ transition: all var(--purpur-motion-duration-150) var(--purpur-motion-easing-ease-in-out);
43
+ }
38
44
  }
39
45
 
40
46
  &:hover {
@@ -110,7 +116,10 @@
110
116
  left: 0;
111
117
  z-index: 10;
112
118
  height: var(--purpur-border-width-sm);
113
- transition: all var(--purpur-motion-duration-150) var(--purpur-motion-easing-ease-in-out);
119
+
120
+ @media (prefers-reduced-motion: no-preference) {
121
+ transition: all var(--purpur-motion-duration-150) var(--purpur-motion-easing-ease-in-out);
122
+ }
114
123
  }
115
124
  }
116
125
 
@@ -134,6 +143,14 @@
134
143
  }
135
144
  }
136
145
 
146
+ &__content-wrapper {
147
+ overflow: hidden;
148
+
149
+ @media (prefers-reduced-motion: no-preference) {
150
+ transition: height var(--purpur-motion-duration-200) var(--purpur-motion-easing-ease-in-out);
151
+ }
152
+ }
153
+
137
154
  &--contained,
138
155
  &--contained-negative {
139
156
  #{$root}__list {
@@ -1,9 +1,12 @@
1
1
  import React from "react";
2
2
  import { Button } from "@purpurds/button";
3
+ import { Paragraph } from "@purpurds/paragraph";
3
4
  import { useArgs } from "@storybook/preview-api";
4
5
  import type { Meta, StoryObj } from "@storybook/react";
5
6
 
6
7
  import "@purpurds/button/styles";
8
+ import "@purpurds/icon/styles";
9
+ import "@purpurds/paragraph/styles";
7
10
  import { Tabs } from "./tabs";
8
11
  import { tabsVariants } from "./tabs.utils";
9
12
 
@@ -61,6 +64,11 @@ const meta = {
61
64
  negative: {
62
65
  table: { type: { summary: "boolean" } },
63
66
  },
67
+ animateHeight: {
68
+ table: {
69
+ type: { summary: "When true, animates the height of the component when switching tabs." },
70
+ },
71
+ },
64
72
  ["data-testid"]: { table: { type: { summary: "string" } }, control: { type: "text" } },
65
73
  className: {
66
74
  table: {
@@ -89,7 +97,13 @@ export const Showcase: Story = {
89
97
  name={`${name}-1`}
90
98
  style={{ padding: "var(--purpur-spacing-250)" }}
91
99
  >
92
- <div>Content 1</div>
100
+ <div>
101
+ <Paragraph>
102
+ Telia is a leading telecom provider in the Nordics and Baltics, offering mobile,
103
+ broadband, and TV services to millions of customers. Known for its reliability and
104
+ coverage, Telia plays a key role in keeping people and businesses connected.
105
+ </Paragraph>
106
+ </div>
93
107
  </Tabs.Content>,
94
108
  <Tabs.Content
95
109
  key="2"
@@ -97,7 +111,18 @@ export const Showcase: Story = {
97
111
  name={`${name}-2`}
98
112
  style={{ padding: "var(--purpur-spacing-250)" }}
99
113
  >
100
- <div>Content 2</div>
114
+ <div>
115
+ <Paragraph style={{ marginBottom: "var(--purpur-spacing-200)" }}>
116
+ Telia is at the forefront of digital innovation in Northern Europe, investing heavily in
117
+ 5G networks, fiber broadband, and cloud-based business solutions. Its technology powers
118
+ communication for individuals, families, and companies across the region.
119
+ </Paragraph>
120
+ <Paragraph>
121
+ Beyond connectivity, Telia also offers advanced tools for remote work, IoT, and
122
+ cybersecurity. The company supports both public and private sector partners with
123
+ scalable, secure, and future-ready digital infrastructure.
124
+ </Paragraph>
125
+ </div>
101
126
  </Tabs.Content>,
102
127
  <Tabs.Content
103
128
  key="3"
@@ -105,10 +130,28 @@ export const Showcase: Story = {
105
130
  name={`${name}-3`}
106
131
  style={{ padding: "var(--purpur-spacing-250)" }}
107
132
  >
108
- <div>Content 3</div>
133
+ <div>
134
+ <Paragraph style={{ marginBottom: "var(--purpur-spacing-200)" }}>
135
+ Telia has a long-standing history in telecommunications, with roots stretching back to
136
+ the early days of phone services in Sweden. Today, it operates in multiple countries and
137
+ continues to evolve alongside the needs of a digital society.
138
+ </Paragraph>
139
+ <Paragraph style={{ marginBottom: "var(--purpur-spacing-200)" }}>
140
+ A major focus for Telia is sustainability. The company is working to reduce its
141
+ environmental impact through energy-efficient networks, circular economy initiatives,
142
+ and climate-smart services. Its goal is to become climate-neutral across its entire
143
+ value chain.
144
+ </Paragraph>
145
+ <Paragraph>
146
+ In addition to its consumer offerings, Telia collaborates closely with governments,
147
+ cities, and enterprises. From smart city solutions to secure connectivity for remote
148
+ healthcare, Telia is helping build the digital infrastructure of tomorrow.
149
+ </Paragraph>
150
+ </div>
109
151
  </Tabs.Content>,
110
152
  ],
111
153
  negative: false,
154
+ animateHeight: false,
112
155
  },
113
156
  render: ({ children, ...args }) => (
114
157
  <div
package/src/tabs.tsx CHANGED
@@ -1,4 +1,4 @@
1
- import React, { Children, ReactElement, useEffect, useRef, useState } from "react";
1
+ import React, { Children, type ReactElement, useEffect, useRef, useState } from "react";
2
2
  import { Icon } from "@purpurds/icon";
3
3
  import { chevronLeft } from "@purpurds/icon/assets/chevron-left";
4
4
  import { chevronRight } from "@purpurds/icon/assets/chevron-right";
@@ -8,7 +8,12 @@ import c from "classnames/bind";
8
8
  import { isTabContent, TabContent } from "./tab-content";
9
9
  import { TabHeader } from "./tab-header";
10
10
  import styles from "./tabs.module.scss";
11
- import { createTabChangeDetailEvent, TabChangeDetail, TabsCmp, TabsVariant } from "./tabs.utils";
11
+ import {
12
+ createTabChangeDetailEvent,
13
+ type TabChangeDetail,
14
+ type TabsCmp,
15
+ type TabsVariant,
16
+ } from "./tabs.utils";
12
17
 
13
18
  type TabsProps = {
14
19
  children: Array<ReactElement<typeof TabContent>> | ReactElement<typeof TabContent>;
@@ -24,6 +29,10 @@ type TabsProps = {
24
29
  * */
25
30
  onChange?: (event: CustomEvent<TabChangeDetail>) => void;
26
31
  defaultValue?: string;
32
+ /**
33
+ * When true, animates the height of the component when switching tabs.
34
+ * */
35
+ animateHeight?: boolean;
27
36
  "data-testid"?: string;
28
37
  } & Pick<React.HTMLAttributes<HTMLDivElement>, "style" | "className">;
29
38
 
@@ -77,10 +86,13 @@ export const Tabs: TabsCmp<TabsProps> = ({
77
86
  onChange,
78
87
  className,
79
88
  defaultValue,
89
+ animateHeight = false,
80
90
  "data-testid": dataTestId,
81
91
  value,
82
92
  ...props
83
93
  }) => {
94
+ const contentRef = useRef<HTMLDivElement | null>(null);
95
+ const [height, setHeight] = useState(-1);
84
96
  const [scrollClasses, setScrollClasses] = useState<{ [key: string]: boolean }>({});
85
97
  const [selectedTriggerOffset, setSelectedTriggerOffset] = useState(0);
86
98
  const [selectedTriggerWidth, setSelectedTriggerWidth] = useState(0);
@@ -160,12 +172,26 @@ export const Tabs: TabsCmp<TabsProps> = ({
160
172
  );
161
173
 
162
174
  useEffect(() => {
163
- window.addEventListener("resize", handleLinePosition);
175
+ const setNewHeight = () => {
176
+ if (contentRef.current && animateHeight) {
177
+ const newHeight = contentRef.current.offsetHeight;
178
+ setHeight(newHeight);
179
+ }
180
+ };
181
+
182
+ const handleResize = () => {
183
+ setNewHeight();
184
+ handleLinePosition();
185
+ };
186
+
187
+ setNewHeight();
188
+
189
+ window.addEventListener("resize", handleResize);
164
190
 
165
191
  return () => {
166
- window.removeEventListener("resize", handleLinePosition);
192
+ window.removeEventListener("resize", handleResize);
167
193
  };
168
- }, [activeIndex]); // eslint-disable-line react-hooks/exhaustive-deps
194
+ }, [activeIndex, animateHeight]); // eslint-disable-line react-hooks/exhaustive-deps
169
195
 
170
196
  useEffect(() => {
171
197
  handleLinePosition();
@@ -266,8 +292,13 @@ export const Tabs: TabsCmp<TabsProps> = ({
266
292
  <ScrollButton side="left" />
267
293
  <ScrollButton side="right" />
268
294
  </div>
269
- <div className={cx(`${rootClassName}__content-container`)}>
270
- {Children.map(tabContentChildren, (child) => child)}
295
+ <div
296
+ className={cx(`${rootClassName}__content-wrapper`)}
297
+ style={{ height: animateHeight && height > -1 ? `${height}px` : "auto" }}
298
+ >
299
+ <div ref={contentRef} className={cx(`${rootClassName}__content-container`)}>
300
+ {Children.map(tabContentChildren, (child) => child)}
301
+ </div>
271
302
  </div>
272
303
  </div>
273
304
  </Root>