@khanacademy/wonder-blocks-tabs 0.1.1 → 0.2.1
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/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @khanacademy/wonder-blocks-tabs
|
|
2
2
|
|
|
3
|
+
## 0.2.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 86e1901: Override border on different states (focus-visible, hover, press) to account for the `IconButton` change from `outline` to `border` + `boxShadow`.
|
|
8
|
+
|
|
9
|
+
## 0.2.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- 27f6298: `NavigationTabs`: Add `animated` prop to enable transition animation. Defaults to false.
|
|
14
|
+
|
|
3
15
|
## 0.1.1
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
|
@@ -49,6 +49,8 @@ export declare const NavigationTabItem: React.ForwardRefExoticComponent<Readonly
|
|
|
49
49
|
/**
|
|
50
50
|
* If the `NavigationTabItem` is the current page. If `true`, current
|
|
51
51
|
* styling and aria-current=page will be applied to the Link.
|
|
52
|
+
*
|
|
53
|
+
* Note: NavigationTabs provides the styling for the current tab item.
|
|
52
54
|
*/
|
|
53
55
|
current?: boolean;
|
|
54
56
|
/**
|
|
@@ -64,4 +64,9 @@ export declare const NavigationTabs: React.ForwardRefExoticComponent<Readonly<im
|
|
|
64
64
|
root?: StyleType;
|
|
65
65
|
list?: StyleType;
|
|
66
66
|
};
|
|
67
|
+
/**
|
|
68
|
+
* Whether to include animation in the `NavigationTabs`. This should be false
|
|
69
|
+
* if the user has `prefers-reduced-motion` opted in. Defaults to `false`.
|
|
70
|
+
*/
|
|
71
|
+
animated?: boolean;
|
|
67
72
|
} & React.RefAttributes<HTMLElement>>;
|
package/dist/es/index.js
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import _extends from '@babel/runtime/helpers/extends';
|
|
2
2
|
import _objectWithoutPropertiesLoose from '@babel/runtime/helpers/objectWithoutPropertiesLoose';
|
|
3
|
-
import { addStyle } from '@khanacademy/wonder-blocks-core';
|
|
3
|
+
import { addStyle, useOnMountEffect, View } from '@khanacademy/wonder-blocks-core';
|
|
4
4
|
import { sizing, semanticColor, breakpoint } from '@khanacademy/wonder-blocks-tokens';
|
|
5
5
|
import { StyleSheet } from 'aphrodite';
|
|
6
6
|
import * as React from 'react';
|
|
7
7
|
import { styles as styles$2 } from '@khanacademy/wonder-blocks-typography';
|
|
8
8
|
|
|
9
|
-
const _excluded$1 = ["id", "testId", "children", "aria-label", "aria-labelledby", "styles"];
|
|
9
|
+
const _excluded$1 = ["id", "testId", "children", "aria-label", "aria-labelledby", "styles", "animated"];
|
|
10
10
|
const StyledNav = addStyle("nav");
|
|
11
11
|
const StyledUl = addStyle("ul");
|
|
12
|
+
const StyledDiv = addStyle("div");
|
|
12
13
|
const NavigationTabs = React.forwardRef(function NavigationTabs(props, ref) {
|
|
13
14
|
const {
|
|
14
15
|
id,
|
|
@@ -16,9 +17,56 @@ const NavigationTabs = React.forwardRef(function NavigationTabs(props, ref) {
|
|
|
16
17
|
children,
|
|
17
18
|
"aria-label": ariaLabel,
|
|
18
19
|
"aria-labelledby": ariaLabelledBy,
|
|
19
|
-
styles: stylesProp
|
|
20
|
+
styles: stylesProp,
|
|
21
|
+
animated = false
|
|
20
22
|
} = props,
|
|
21
23
|
otherProps = _objectWithoutPropertiesLoose(props, _excluded$1);
|
|
24
|
+
const listRef = React.useRef(null);
|
|
25
|
+
const indicatorIsReady = React.useRef(false);
|
|
26
|
+
const [underlineStyle, setUnderlineStyle] = React.useState({
|
|
27
|
+
left: 0,
|
|
28
|
+
width: 0
|
|
29
|
+
});
|
|
30
|
+
const updateUnderlineStyle = React.useCallback(() => {
|
|
31
|
+
if (!listRef.current) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const activeTab = Array.from(listRef.current.children).find(child => {
|
|
35
|
+
var _child$children$;
|
|
36
|
+
return (_child$children$ = child.children[0]) == null ? void 0 : _child$children$.ariaCurrent;
|
|
37
|
+
});
|
|
38
|
+
if (activeTab) {
|
|
39
|
+
const tabRect = activeTab.getBoundingClientRect();
|
|
40
|
+
const parentRect = listRef.current.getBoundingClientRect();
|
|
41
|
+
const zoomFactor = parentRect.width / listRef.current.offsetWidth;
|
|
42
|
+
const left = (tabRect.left - parentRect.left) / zoomFactor;
|
|
43
|
+
const width = tabRect.width / zoomFactor;
|
|
44
|
+
setUnderlineStyle({
|
|
45
|
+
left,
|
|
46
|
+
width
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}, [setUnderlineStyle, listRef]);
|
|
50
|
+
useOnMountEffect(() => {
|
|
51
|
+
if (!listRef.current || !window.ResizeObserver) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const observer = new window.ResizeObserver(([entry]) => {
|
|
55
|
+
if (entry) {
|
|
56
|
+
updateUnderlineStyle();
|
|
57
|
+
if (!indicatorIsReady.current) {
|
|
58
|
+
indicatorIsReady.current = true;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
observer.observe(listRef.current);
|
|
63
|
+
return () => {
|
|
64
|
+
observer.disconnect();
|
|
65
|
+
};
|
|
66
|
+
});
|
|
67
|
+
React.useEffect(() => {
|
|
68
|
+
updateUnderlineStyle();
|
|
69
|
+
}, [children, updateUnderlineStyle]);
|
|
22
70
|
return React.createElement(StyledNav, _extends({
|
|
23
71
|
id: id,
|
|
24
72
|
"data-testid": testId,
|
|
@@ -26,14 +74,26 @@ const NavigationTabs = React.forwardRef(function NavigationTabs(props, ref) {
|
|
|
26
74
|
"aria-labelledby": ariaLabelledBy,
|
|
27
75
|
ref: ref,
|
|
28
76
|
style: [styles$1.nav, stylesProp == null ? void 0 : stylesProp.root]
|
|
29
|
-
}, otherProps), React.createElement(
|
|
30
|
-
style:
|
|
31
|
-
},
|
|
77
|
+
}, otherProps), React.createElement(StyledDiv, {
|
|
78
|
+
style: styles$1.contents
|
|
79
|
+
}, React.createElement(StyledUl, {
|
|
80
|
+
style: [styles$1.list, stylesProp == null ? void 0 : stylesProp.list],
|
|
81
|
+
ref: listRef
|
|
82
|
+
}, children), indicatorIsReady.current && React.createElement(View, {
|
|
83
|
+
style: [{
|
|
84
|
+
transform: `translateX(${underlineStyle.left}px)`,
|
|
85
|
+
width: `${underlineStyle.width}px`
|
|
86
|
+
}, styles$1.currentUnderline, animated && styles$1.underlineTransition],
|
|
87
|
+
role: "presentation"
|
|
88
|
+
})));
|
|
32
89
|
});
|
|
33
90
|
const styles$1 = StyleSheet.create({
|
|
34
91
|
nav: {
|
|
35
92
|
overflowX: "auto"
|
|
36
93
|
},
|
|
94
|
+
contents: {
|
|
95
|
+
position: "relative"
|
|
96
|
+
},
|
|
37
97
|
list: {
|
|
38
98
|
paddingInline: sizing.size_050,
|
|
39
99
|
paddingBlock: sizing.size_0,
|
|
@@ -41,6 +101,16 @@ const styles$1 = StyleSheet.create({
|
|
|
41
101
|
display: "flex",
|
|
42
102
|
gap: sizing.size_200,
|
|
43
103
|
flexWrap: "nowrap"
|
|
104
|
+
},
|
|
105
|
+
currentUnderline: {
|
|
106
|
+
position: "absolute",
|
|
107
|
+
bottom: 0,
|
|
108
|
+
left: 0,
|
|
109
|
+
height: sizing.size_050,
|
|
110
|
+
backgroundColor: semanticColor.action.secondary.progressive.default.foreground
|
|
111
|
+
},
|
|
112
|
+
underlineTransition: {
|
|
113
|
+
transition: "transform 0.3s ease, width 0.3s ease"
|
|
44
114
|
}
|
|
45
115
|
});
|
|
46
116
|
|
|
@@ -90,9 +160,8 @@ const styles = StyleSheet.create({
|
|
|
90
160
|
}
|
|
91
161
|
},
|
|
92
162
|
current: {
|
|
93
|
-
boxShadow: `inset 0 -${sizing.size_050} 0 0 ${semanticColor.action.secondary.progressive.default.foreground}`,
|
|
94
163
|
[":has(a:hover)"]: {
|
|
95
|
-
boxShadow:
|
|
164
|
+
boxShadow: "none"
|
|
96
165
|
}
|
|
97
166
|
},
|
|
98
167
|
currentLink: {
|
|
@@ -108,17 +177,20 @@ const styles = StyleSheet.create({
|
|
|
108
177
|
textDecoration: "none",
|
|
109
178
|
":hover": {
|
|
110
179
|
textDecoration: "none",
|
|
180
|
+
border: "none",
|
|
111
181
|
outline: "none",
|
|
112
182
|
color: semanticColor.action.secondary.progressive.default.foreground,
|
|
113
183
|
backgroundColor: "transparent"
|
|
114
184
|
},
|
|
115
185
|
":active": {
|
|
116
186
|
textDecoration: "none",
|
|
187
|
+
border: "none",
|
|
117
188
|
outline: "none",
|
|
118
189
|
color: semanticColor.action.secondary.progressive.press.foreground
|
|
119
190
|
},
|
|
120
191
|
":focus-visible": {
|
|
121
192
|
color: semanticColor.action.secondary.progressive.default.foreground,
|
|
193
|
+
border: "none",
|
|
122
194
|
outline: "none",
|
|
123
195
|
boxShadow: `0 0 0 ${sizing.size_025} ${semanticColor.focus.inner}, 0 0 0 ${sizing.size_050} ${semanticColor.focus.outer}`,
|
|
124
196
|
borderRadius: 0
|
package/dist/index.js
CHANGED
|
@@ -34,9 +34,10 @@ var _extends__default = /*#__PURE__*/_interopDefaultLegacy(_extends);
|
|
|
34
34
|
var _objectWithoutPropertiesLoose__default = /*#__PURE__*/_interopDefaultLegacy(_objectWithoutPropertiesLoose);
|
|
35
35
|
var React__namespace = /*#__PURE__*/_interopNamespace(React);
|
|
36
36
|
|
|
37
|
-
const _excluded$1 = ["id", "testId", "children", "aria-label", "aria-labelledby", "styles"];
|
|
37
|
+
const _excluded$1 = ["id", "testId", "children", "aria-label", "aria-labelledby", "styles", "animated"];
|
|
38
38
|
const StyledNav = wonderBlocksCore.addStyle("nav");
|
|
39
39
|
const StyledUl = wonderBlocksCore.addStyle("ul");
|
|
40
|
+
const StyledDiv = wonderBlocksCore.addStyle("div");
|
|
40
41
|
const NavigationTabs = React__namespace.forwardRef(function NavigationTabs(props, ref) {
|
|
41
42
|
const {
|
|
42
43
|
id,
|
|
@@ -44,9 +45,56 @@ const NavigationTabs = React__namespace.forwardRef(function NavigationTabs(props
|
|
|
44
45
|
children,
|
|
45
46
|
"aria-label": ariaLabel,
|
|
46
47
|
"aria-labelledby": ariaLabelledBy,
|
|
47
|
-
styles: stylesProp
|
|
48
|
+
styles: stylesProp,
|
|
49
|
+
animated = false
|
|
48
50
|
} = props,
|
|
49
51
|
otherProps = _objectWithoutPropertiesLoose__default["default"](props, _excluded$1);
|
|
52
|
+
const listRef = React__namespace.useRef(null);
|
|
53
|
+
const indicatorIsReady = React__namespace.useRef(false);
|
|
54
|
+
const [underlineStyle, setUnderlineStyle] = React__namespace.useState({
|
|
55
|
+
left: 0,
|
|
56
|
+
width: 0
|
|
57
|
+
});
|
|
58
|
+
const updateUnderlineStyle = React__namespace.useCallback(() => {
|
|
59
|
+
if (!listRef.current) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const activeTab = Array.from(listRef.current.children).find(child => {
|
|
63
|
+
var _child$children$;
|
|
64
|
+
return (_child$children$ = child.children[0]) == null ? void 0 : _child$children$.ariaCurrent;
|
|
65
|
+
});
|
|
66
|
+
if (activeTab) {
|
|
67
|
+
const tabRect = activeTab.getBoundingClientRect();
|
|
68
|
+
const parentRect = listRef.current.getBoundingClientRect();
|
|
69
|
+
const zoomFactor = parentRect.width / listRef.current.offsetWidth;
|
|
70
|
+
const left = (tabRect.left - parentRect.left) / zoomFactor;
|
|
71
|
+
const width = tabRect.width / zoomFactor;
|
|
72
|
+
setUnderlineStyle({
|
|
73
|
+
left,
|
|
74
|
+
width
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}, [setUnderlineStyle, listRef]);
|
|
78
|
+
wonderBlocksCore.useOnMountEffect(() => {
|
|
79
|
+
if (!listRef.current || !window.ResizeObserver) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
const observer = new window.ResizeObserver(([entry]) => {
|
|
83
|
+
if (entry) {
|
|
84
|
+
updateUnderlineStyle();
|
|
85
|
+
if (!indicatorIsReady.current) {
|
|
86
|
+
indicatorIsReady.current = true;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
observer.observe(listRef.current);
|
|
91
|
+
return () => {
|
|
92
|
+
observer.disconnect();
|
|
93
|
+
};
|
|
94
|
+
});
|
|
95
|
+
React__namespace.useEffect(() => {
|
|
96
|
+
updateUnderlineStyle();
|
|
97
|
+
}, [children, updateUnderlineStyle]);
|
|
50
98
|
return React__namespace.createElement(StyledNav, _extends__default["default"]({
|
|
51
99
|
id: id,
|
|
52
100
|
"data-testid": testId,
|
|
@@ -54,14 +102,26 @@ const NavigationTabs = React__namespace.forwardRef(function NavigationTabs(props
|
|
|
54
102
|
"aria-labelledby": ariaLabelledBy,
|
|
55
103
|
ref: ref,
|
|
56
104
|
style: [styles$1.nav, stylesProp == null ? void 0 : stylesProp.root]
|
|
57
|
-
}, otherProps), React__namespace.createElement(
|
|
58
|
-
style:
|
|
59
|
-
},
|
|
105
|
+
}, otherProps), React__namespace.createElement(StyledDiv, {
|
|
106
|
+
style: styles$1.contents
|
|
107
|
+
}, React__namespace.createElement(StyledUl, {
|
|
108
|
+
style: [styles$1.list, stylesProp == null ? void 0 : stylesProp.list],
|
|
109
|
+
ref: listRef
|
|
110
|
+
}, children), indicatorIsReady.current && React__namespace.createElement(wonderBlocksCore.View, {
|
|
111
|
+
style: [{
|
|
112
|
+
transform: `translateX(${underlineStyle.left}px)`,
|
|
113
|
+
width: `${underlineStyle.width}px`
|
|
114
|
+
}, styles$1.currentUnderline, animated && styles$1.underlineTransition],
|
|
115
|
+
role: "presentation"
|
|
116
|
+
})));
|
|
60
117
|
});
|
|
61
118
|
const styles$1 = aphrodite.StyleSheet.create({
|
|
62
119
|
nav: {
|
|
63
120
|
overflowX: "auto"
|
|
64
121
|
},
|
|
122
|
+
contents: {
|
|
123
|
+
position: "relative"
|
|
124
|
+
},
|
|
65
125
|
list: {
|
|
66
126
|
paddingInline: wonderBlocksTokens.sizing.size_050,
|
|
67
127
|
paddingBlock: wonderBlocksTokens.sizing.size_0,
|
|
@@ -69,6 +129,16 @@ const styles$1 = aphrodite.StyleSheet.create({
|
|
|
69
129
|
display: "flex",
|
|
70
130
|
gap: wonderBlocksTokens.sizing.size_200,
|
|
71
131
|
flexWrap: "nowrap"
|
|
132
|
+
},
|
|
133
|
+
currentUnderline: {
|
|
134
|
+
position: "absolute",
|
|
135
|
+
bottom: 0,
|
|
136
|
+
left: 0,
|
|
137
|
+
height: wonderBlocksTokens.sizing.size_050,
|
|
138
|
+
backgroundColor: wonderBlocksTokens.semanticColor.action.secondary.progressive.default.foreground
|
|
139
|
+
},
|
|
140
|
+
underlineTransition: {
|
|
141
|
+
transition: "transform 0.3s ease, width 0.3s ease"
|
|
72
142
|
}
|
|
73
143
|
});
|
|
74
144
|
|
|
@@ -118,9 +188,8 @@ const styles = aphrodite.StyleSheet.create({
|
|
|
118
188
|
}
|
|
119
189
|
},
|
|
120
190
|
current: {
|
|
121
|
-
boxShadow: `inset 0 -${wonderBlocksTokens.sizing.size_050} 0 0 ${wonderBlocksTokens.semanticColor.action.secondary.progressive.default.foreground}`,
|
|
122
191
|
[":has(a:hover)"]: {
|
|
123
|
-
boxShadow:
|
|
192
|
+
boxShadow: "none"
|
|
124
193
|
}
|
|
125
194
|
},
|
|
126
195
|
currentLink: {
|
|
@@ -136,17 +205,20 @@ const styles = aphrodite.StyleSheet.create({
|
|
|
136
205
|
textDecoration: "none",
|
|
137
206
|
":hover": {
|
|
138
207
|
textDecoration: "none",
|
|
208
|
+
border: "none",
|
|
139
209
|
outline: "none",
|
|
140
210
|
color: wonderBlocksTokens.semanticColor.action.secondary.progressive.default.foreground,
|
|
141
211
|
backgroundColor: "transparent"
|
|
142
212
|
},
|
|
143
213
|
":active": {
|
|
144
214
|
textDecoration: "none",
|
|
215
|
+
border: "none",
|
|
145
216
|
outline: "none",
|
|
146
217
|
color: wonderBlocksTokens.semanticColor.action.secondary.progressive.press.foreground
|
|
147
218
|
},
|
|
148
219
|
":focus-visible": {
|
|
149
220
|
color: wonderBlocksTokens.semanticColor.action.secondary.progressive.default.foreground,
|
|
221
|
+
border: "none",
|
|
150
222
|
outline: "none",
|
|
151
223
|
boxShadow: `0 0 0 ${wonderBlocksTokens.sizing.size_025} ${wonderBlocksTokens.semanticColor.focus.inner}, 0 0 0 ${wonderBlocksTokens.sizing.size_050} ${wonderBlocksTokens.semanticColor.focus.outer}`,
|
|
152
224
|
borderRadius: 0
|