@jetbrains/kotlin-web-site-ui 4.2.0-alpha.2 → 4.2.0-alpha.4

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,4 +1,5 @@
1
- import React__default, { useState, useContext, useRef, useCallback, useLayoutEffect } from 'react';
1
+ import React__default, { useState, useContext, useRef, useCallback, useLayoutEffect, useEffect } from 'react';
2
+ import { disableBodyScroll, clearAllBodyScrollLocks } from 'body-scroll-lock';
2
3
  import useScrollbarSize from 'react-scrollbar-size';
3
4
  import { ThemeProvider } from '@rescui/ui-contexts';
4
5
  import Input from '@rescui/input';
@@ -53,6 +54,12 @@ const FullSearch = ({
53
54
  }
54
55
  }
55
56
  }, [hits, inputValue]);
57
+ useLayoutEffect(() => {
58
+ if (ref.current) disableBodyScroll(ref.current);
59
+ }, [ref]);
60
+ useEffect(() => {
61
+ return clearAllBodyScrollLocks;
62
+ }, []);
56
63
  return React__default.createElement(ThemeProvider, {
57
64
  theme: 'light'
58
65
  }, React__default.createElement("div", {
@@ -1,7 +1,6 @@
1
1
  import React__default, { forwardRef, useState, useMemo, useRef, useImperativeHandle, useLayoutEffect, useCallback, useEffect } from 'react';
2
2
  import { ThemeProvider } from '@rescui/ui-contexts';
3
3
  import FocusManager from '@rescui/focus-manager';
4
- import useResizeObserver from '@react-hook/resize-observer';
5
4
  import OutsideClickHandler from 'react-outside-click-handler';
6
5
  import { LogoLarge } from './logo-large/logo-large.js';
7
6
  import styles from './header.module.pcss.js';
@@ -15,7 +14,8 @@ import classNames from 'classnames';
15
14
  import { SearchWrapper } from './search-wrapper/search-wrapper.js';
16
15
  import { isMacOs } from './is-macos.js';
17
16
  import { KEY_K_CODE } from './key-codes.js';
18
- const MENU_POPUP_BREAKPOINT = 640;
17
+ import useResizeObserver from '@react-hook/resize-observer';
18
+ const BREAKPOINT_XS = 640;
19
19
  const Header = forwardRef(({
20
20
  productWebUrl,
21
21
  hasSearch = false,
@@ -27,21 +27,20 @@ const Header = forwardRef(({
27
27
  linkHandler,
28
28
  isPlayground,
29
29
  isUrlActive,
30
- searchConfig,
31
- noScrollClassName
30
+ searchConfig
32
31
  }, forwardedRef) => {
33
32
  const [menuPopupExpanded, setMenuPopupExpanded] = useState(false);
34
- const [isMenuPopupVisible, setIsMenuPopupVisible] = useState(false);
35
33
  const [isSearchBoxVisible, setSearchBoxVisible] = useState(false);
36
34
  const [fullSearchActive, setFullSearchActive] = useState(false);
37
35
  const items = useMemo(() => getNavScheme(isUrlActive, currentUrl, isPlayground), [isUrlActive, currentUrl, isPlayground]);
38
36
  const itemsWithoutHomeSection = useMemo(() => items.slice(1), [items]);
39
37
  const headerRef = useRef(null);
40
38
  useImperativeHandle(forwardedRef, () => headerRef.current);
39
+ const [isMenuPopupVisible, setMenuPopupVisible] = useState(false);
41
40
  useLayoutEffect(() => {
42
- setIsMenuPopupVisible((headerRef.current?.getBoundingClientRect?.().width ?? 0) <= MENU_POPUP_BREAKPOINT);
41
+ setMenuPopupVisible((headerRef.current?.getBoundingClientRect?.().width ?? 0) <= BREAKPOINT_XS);
43
42
  }, [headerRef]);
44
- useResizeObserver(headerRef, entry => setIsMenuPopupVisible(entry.contentRect.width <= MENU_POPUP_BREAKPOINT));
43
+ useResizeObserver(headerRef, entry => setMenuPopupVisible(entry.contentRect.width <= BREAKPOINT_XS));
45
44
  const handleSearchButtonClick = useCallback(() => {
46
45
  setSearchBoxVisible(!isSearchBoxVisible);
47
46
  }, []);
@@ -70,15 +69,6 @@ const Header = forwardRef(({
70
69
  };
71
70
  }
72
71
  }, [fullSearchKeyHandler]);
73
- useEffect(() => {
74
- if (typeof document !== `undefined` && noScrollClassName) {
75
- if (fullSearchActive) {
76
- document.body.classList.add(noScrollClassName);
77
- } else {
78
- document.body.classList.remove(noScrollClassName);
79
- }
80
- }
81
- }, [fullSearchActive]);
82
72
  return React__default.createElement(ThemeProvider, {
83
73
  theme: 'dark'
84
74
  }, React__default.createElement(SearchWrapper, {
@@ -75,12 +75,6 @@
75
75
  color: var(--ktl-color-primary-light-theme);
76
76
  }
77
77
 
78
- @media (max-width: 640px) {
79
- .ktl-horizontal-menu-module_horizontal-menu_pB2-S {
80
- display: none;
81
- }
82
- }
83
-
84
78
  :root {
85
79
  --ktl-light-grey: #f4f4f4;
86
80
  --ktl-dark-100: rgba(39, 40, 44, 1);
@@ -102,24 +96,8 @@
102
96
  --rs-font-family-ui: var(--ktl-font-family-inter);
103
97
  }
104
98
 
105
- .ktl-dropdown-menu-module_overlay_segRo {
106
- display: none;
107
- position: fixed;
108
- top: 0;
109
- right: 0;
110
- bottom: 0;
111
- left: 0;
112
- width: 100%;
113
- height: 100%;
114
- opacity: 0;
115
- background-color: var(--ktl-color-dark-40);
116
- z-index: var(--ktl-overlay-z-index);
117
- -webkit-animation: ktl-dropdown-menu-module_fadein_MySnq ease-out var(--ktl-transition-fast) forwards;
118
- animation: ktl-dropdown-menu-module_fadein_MySnq ease-out var(--ktl-transition-fast) forwards;
119
- }
120
-
121
- .ktl-dropdown-menu-module_dropdown-menu_tq2uU {
122
- display: none;
99
+ .ktl-vertical-menu-module_vertical-menu_aLIbw {
100
+ display: block;
123
101
  height: 100%;
124
102
  justify-self: flex-start;
125
103
  white-space: nowrap;
@@ -128,7 +106,7 @@
128
106
  max-width: 100%;
129
107
  }
130
108
 
131
- .ktl-dropdown-menu-module_button_OYsuv {
109
+ .ktl-vertical-menu-module_button_zqr20 {
132
110
  border: none;
133
111
  background: none;
134
112
  height: 100%;
@@ -141,13 +119,13 @@
141
119
  cursor: pointer;
142
120
  }
143
121
 
144
- .ktl-dropdown-menu-module_button-text_SJmh- {
122
+ .ktl-vertical-menu-module_button-text_aXith {
145
123
  overflow: hidden;
146
124
  text-overflow: ellipsis;
147
125
  flex: 0 1 auto;
148
126
  }
149
127
 
150
- .ktl-dropdown-menu-module_icon_GGhMI {
128
+ .ktl-vertical-menu-module_icon_-Ieat {
151
129
  width: 12px;
152
130
  height: 6px;
153
131
  margin-left: 6px;
@@ -155,7 +133,9 @@
155
133
  flex: 0 0 auto;
156
134
  }
157
135
 
158
- .ktl-dropdown-menu-module_dropdown-header_fa92T {
136
+ .ktl-vertical-menu-module_dropdown-header_77lTy {
137
+ /* workaround */
138
+ margin-top: calc(var(--ktl-header-height-mobile) * -1);
159
139
  display: flex;
160
140
  color: #ffffff;
161
141
  align-items: center;
@@ -163,88 +143,58 @@
163
143
  padding: 0 0 0 16px;
164
144
  border-bottom: 1px solid rgba(255, 255, 255, 0.3);
165
145
  justify-content: space-between;
146
+ background: #ffffff;
166
147
  }
167
148
 
168
- .ktl-dropdown-menu-module_dropdown-list_Ylkvt {
169
- display: none;
149
+ .ktl-vertical-menu-module_dropdown-list_N3KWV {
150
+ display: flex;
151
+ flex-direction: column;
152
+ opacity: 1;
170
153
  position: fixed;
171
154
  top: 0;
172
155
  left: 0;
173
156
  right: 0;
174
157
  bottom: 0;
175
158
  background: #ffffff;
176
- opacity: 0;
177
- -webkit-animation: ktl-dropdown-menu-module_fadein_MySnq ease-out var(--ktl-transition-fast) forwards;
178
- animation: ktl-dropdown-menu-module_fadein_MySnq ease-out var(--ktl-transition-fast) forwards;
179
159
  z-index: var(--ktl-mobile-dropdown-list-z-index);
180
160
  }
181
161
 
182
- .ktl-dropdown-menu-module_dropdown-item_X3tZ- {
162
+ .ktl-vertical-menu-module_dropdown-item_XLfp4 {
183
163
  padding: 12px 16px;
184
164
  text-decoration: none;
185
165
  transition: color var(--ktl-transition-xfast),
186
166
  background-color var(--ktl-transition-xfast);
187
167
  }
188
168
 
189
- .ktl-dropdown-menu-module_dropdown-item_X3tZ-:hover {
169
+ .ktl-vertical-menu-module_dropdown-item_XLfp4:hover {
190
170
  background: rgba(39, 40, 44, 0.1);
191
171
  color: #27282c;
192
172
  }
193
173
 
194
- .ktl-dropdown-menu-module_dropdown-item_X3tZ-.ktl-dropdown-menu-module_dropdown-item-active_99q9X {
174
+ .ktl-vertical-menu-module_dropdown-item_XLfp4.ktl-vertical-menu-module_dropdown-item-active_iBUbj {
195
175
  background: var(--ktl-dark-100);
196
176
  color: #ffffff;
197
177
  }
198
178
 
199
- .ktl-dropdown-menu-module_overlay-visible_MjwEF {
200
- display: block;
201
- opacity: 1;
202
- }
203
-
204
- .ktl-dropdown-menu-module_dropdown-menu-expanded_EQefy .ktl-dropdown-menu-module_icon_GGhMI {
179
+ .ktl-vertical-menu-module_vertical-menu-expanded_kFaaI .ktl-vertical-menu-module_icon_-Ieat {
205
180
  transform: scale(1, -1);
206
181
  }
207
182
 
208
- .ktl-dropdown-menu-module_dropdown-menu-expanded_EQefy .ktl-dropdown-menu-module_dropdown-list_Ylkvt {
209
- display: flex;
210
- flex-direction: column;
211
- opacity: 1;
212
- }
213
-
214
- .ktl-dropdown-menu-module_dropdown-list-dark-theme_P60ol {
183
+ .ktl-vertical-menu-module_dropdown-list-dark-theme_A1-Bw {
215
184
  background: rgba(39, 40, 44, 1);
216
185
  }
217
186
 
218
- .ktl-dropdown-menu-module_dropdown-list-dark-theme_P60ol .ktl-dropdown-menu-module_dropdown-item_X3tZ-:hover {
187
+ .ktl-vertical-menu-module_dropdown-list-dark-theme_A1-Bw .ktl-vertical-menu-module_dropdown-header_77lTy {
188
+ background: rgba(39, 40, 44, 1);
189
+ }
190
+
191
+ .ktl-vertical-menu-module_dropdown-list-dark-theme_A1-Bw .ktl-vertical-menu-module_dropdown-item_XLfp4:hover {
219
192
  background: rgba(255, 255, 255, 0.1);
220
193
  color: #ffffff;
221
194
  }
222
195
 
223
- .ktl-dropdown-menu-module_dropdown-list-dark-theme_P60ol .ktl-dropdown-menu-module_dropdown-item_X3tZ-.ktl-dropdown-menu-module_dropdown-item-active_99q9X {
196
+ .ktl-vertical-menu-module_dropdown-list-dark-theme_A1-Bw .ktl-vertical-menu-module_dropdown-item_XLfp4.ktl-vertical-menu-module_dropdown-item-active_iBUbj {
224
197
  color: var(--ktl-light-text-hard);
225
198
  background: #ffffff;
226
199
  }
227
200
 
228
- @media (max-width: 640px) {
229
- .ktl-dropdown-menu-module_dropdown-menu_tq2uU {
230
- display: block;
231
- }
232
- }
233
-
234
- @-webkit-keyframes ktl-dropdown-menu-module_fadein_MySnq {
235
- 0% {
236
- opacity: 0;
237
- }
238
- 100% {
239
- opacity: 1;
240
- }
241
- }
242
-
243
- @keyframes ktl-dropdown-menu-module_fadein_MySnq {
244
- 0% {
245
- opacity: 0;
246
- }
247
- 100% {
248
- opacity: 1;
249
- }
250
- }
@@ -1,12 +1,13 @@
1
- import React__default from 'react';
1
+ import React__default, { forwardRef, useRef, useImperativeHandle, useState, useLayoutEffect } from 'react';
2
2
  import { useTheme } from '@rescui/ui-contexts';
3
3
  import classNames from 'classnames';
4
4
  import styles from './top-menu.module.pcss.js';
5
5
  import { useTextStyles } from '@rescui/typography';
6
6
  import { HorizontalMenu } from './horizontal-menu/horizontal-menu.js';
7
- import { DropdownMenu } from './dropdown-menu/dropdown-menu.js';
8
-
9
- const TopMenu = ({
7
+ import { VerticalMenu } from './vertical-menu/vertical-menu.js';
8
+ import useResizeObserver from '@react-hook/resize-observer';
9
+ const BREAKPOINT_XS = 640;
10
+ const TopMenu = forwardRef(({
10
11
  homeUrl,
11
12
  title,
12
13
  mobileTitle,
@@ -15,33 +16,39 @@ const TopMenu = ({
15
16
  linkHandler = () => {},
16
17
  className,
17
18
  children,
18
- forwardedRef,
19
19
  mobileOverview
20
- }) => {
20
+ }, forwardedRef) => {
21
21
  const theme = useTheme();
22
22
  const textCn = useTextStyles();
23
+ const menuRef = useRef(null);
24
+ useImperativeHandle(forwardedRef, () => menuRef.current);
25
+ const [isMobileMenuVisible, setMobileMenuVisible] = useState(false);
26
+ useLayoutEffect(() => {
27
+ setMobileMenuVisible((menuRef.current?.getBoundingClientRect?.().width ?? 0) <= BREAKPOINT_XS);
28
+ }, [menuRef]);
29
+ useResizeObserver(menuRef, entry => setMobileMenuVisible(entry.contentRect.width <= BREAKPOINT_XS));
23
30
  return React__default.createElement("div", {
31
+ ref: menuRef,
24
32
  className: classNames(styles.topMenu, className, {
25
33
  [styles.topMenuDarkTheme]: theme === 'dark'
26
- }),
27
- ref: forwardedRef
34
+ })
28
35
  }, React__default.createElement("a", {
29
36
  href: homeUrl,
30
37
  className: classNames(styles.logo, textCn('rs-h3')),
31
38
  onClick: event => linkHandler(event, homeUrl)
32
- }, title), React__default.createElement(DropdownMenu, {
39
+ }, title), isMobileMenuVisible ? React__default.createElement(VerticalMenu, {
33
40
  items: items,
34
41
  activeIndex: activeIndex,
35
42
  linkHandler: linkHandler,
36
43
  title: title,
37
44
  mobileTitle: mobileTitle,
38
45
  homeUrl: homeUrl,
39
- mobileOverview: mobileOverview
40
- }), React__default.createElement(HorizontalMenu, {
46
+ mobileOverview: mobileOverview,
47
+ topMenuRef: menuRef
48
+ }) : React__default.createElement(HorizontalMenu, {
41
49
  items: items,
42
50
  activeIndex: activeIndex,
43
51
  linkHandler: linkHandler
44
52
  }), children);
45
- };
46
-
53
+ });
47
54
  export { TopMenu };
@@ -1,30 +1,25 @@
1
- import React__default, { useState } from 'react';
2
- import ReactDOM from 'react-dom';
1
+ import React__default, { useState, useCallback, useEffect, useRef, useLayoutEffect } from 'react';
3
2
  import classNames from 'classnames';
4
- import styles from './dropdown-menu.module.pcss.js';
3
+ import styles from './vertical-menu.module.pcss.js';
5
4
  import { useTextStyles } from '@rescui/typography';
6
5
  import SvgArrowDropdownIcon from './arrow-dropdown-icon.svg.js';
7
6
  import { useTheme } from '@rescui/ui-contexts';
8
7
  import Button from '@rescui/button';
9
8
  import { CloseIcon } from '@rescui/icons';
9
+ import { disableBodyScroll, clearAllBodyScrollLocks } from 'body-scroll-lock';
10
+ import { createPortal } from 'react-dom';
10
11
 
11
- const DropdownMenu = ({
12
+ const VerticalMenu = ({
12
13
  homeUrl,
13
14
  title,
14
15
  mobileTitle = 'Overview',
15
16
  items,
16
17
  activeIndex,
17
18
  linkHandler,
18
- mobileOverview = true
19
+ mobileOverview = true,
20
+ topMenuRef
19
21
  }) => {
20
- const theme = useTheme();
21
22
  const textCn = useTextStyles();
22
- const [portalRoot, setPortalRoot] = React__default.useState(null);
23
- React__default.useEffect(() => {
24
- if (typeof document !== `undefined`) {
25
- setPortalRoot(document.body);
26
- }
27
- }, []);
28
23
 
29
24
  let _items = (mobileOverview ? [{
30
25
  title: mobileTitle,
@@ -35,9 +30,18 @@ const DropdownMenu = ({
35
30
 
36
31
  const activeItem = _items[_activeIndex];
37
32
  const [isExpanded, setIsExpanded] = useState(false);
38
-
39
- const handleClick = () => setIsExpanded(!isExpanded);
40
-
33
+ const [portalRoot, setPortalRoot] = useState(null);
34
+ const handleOpen = useCallback(() => {
35
+ setIsExpanded(true);
36
+ }, []);
37
+ const handleClose = useCallback(() => {
38
+ setIsExpanded(false);
39
+ }, []);
40
+ useEffect(() => {
41
+ if (typeof document !== `undefined`) {
42
+ setPortalRoot(document.body);
43
+ }
44
+ }, []);
41
45
  return React__default.createElement(React__default.Fragment, null, React__default.createElement("div", {
42
46
  className: classNames(styles.dropdownMenu, {
43
47
  [styles.dropdownMenuExpanded]: isExpanded
@@ -45,12 +49,50 @@ const DropdownMenu = ({
45
49
  }, React__default.createElement("button", {
46
50
  className: classNames(styles.button, textCn('rs-text-2')),
47
51
  "aria-haspopup": "true",
48
- onClick: handleClick
52
+ onClick: handleOpen
49
53
  }, React__default.createElement("span", {
50
54
  className: styles.buttonText
51
55
  }, activeItem.title), React__default.createElement(SvgArrowDropdownIcon, {
52
56
  className: styles.icon
53
- })), React__default.createElement("nav", {
57
+ })), !!portalRoot && isExpanded && createPortal(React__default.createElement(VerticalMenuDropDown, {
58
+ title: title,
59
+ activeIndex: _activeIndex,
60
+ items: _items,
61
+ onClose: handleClose,
62
+ linkHandler: linkHandler,
63
+ topMenuRef: topMenuRef
64
+ }), portalRoot)));
65
+ };
66
+
67
+ const VerticalMenuDropDown = ({
68
+ title,
69
+ onClose,
70
+ items,
71
+ linkHandler,
72
+ activeIndex,
73
+ topMenuRef
74
+ }) => {
75
+ const textCn = useTextStyles();
76
+ const theme = useTheme();
77
+ const navRef = useRef(null);
78
+ const [navStyle, setNavStyle] = useState({});
79
+ useEffect(() => {
80
+ if (navRef.current) {
81
+ disableBodyScroll(navRef.current);
82
+ }
83
+
84
+ return clearAllBodyScrollLocks;
85
+ }, []);
86
+ useLayoutEffect(() => {
87
+ if (topMenuRef.current) {
88
+ setNavStyle({
89
+ top: topMenuRef.current.getBoundingClientRect().top
90
+ });
91
+ }
92
+ }, [topMenuRef]);
93
+ return React__default.createElement("nav", {
94
+ ref: navRef,
95
+ style: navStyle,
54
96
  className: classNames(styles.dropdownList, {
55
97
  [styles.dropdownListDarkTheme]: theme === 'dark'
56
98
  })
@@ -62,21 +104,16 @@ const DropdownMenu = ({
62
104
  mode: 'clear',
63
105
  size: 'l',
64
106
  icon: React__default.createElement(CloseIcon, null),
65
- onClick: handleClick
66
- })), _items.map((item, index) => React__default.createElement("a", {
107
+ onClick: onClose
108
+ })), items.map((item, index) => React__default.createElement("a", {
67
109
  key: item.url,
68
110
  href: item.url,
69
111
  className: classNames(styles.dropdownItem, textCn('rs-text-1'), {
70
- [styles.dropdownItemActive]: index === _activeIndex
112
+ [styles.dropdownItemActive]: index === activeIndex
71
113
  }),
72
114
  onClick: event => linkHandler?.(event, item.url),
73
115
  target: item.isExternal ? '_blank' : undefined
74
- }, item.title)))), !!portalRoot && ReactDOM.createPortal(React__default.createElement("div", {
75
- className: classNames(styles.overlay, {
76
- [styles.overlayVisible]: isExpanded
77
- }),
78
- onClick: handleClick
79
- }), portalRoot));
116
+ }, item.title)));
80
117
  };
81
118
 
82
- export { DropdownMenu };
119
+ export { VerticalMenu };
@@ -0,0 +1,13 @@
1
+ var styles = {
2
+ "verticalMenu": "ktl-vertical-menu-module_vertical-menu_aLIbw",
3
+ "button": "ktl-vertical-menu-module_button_zqr20",
4
+ "buttonText": "ktl-vertical-menu-module_button-text_aXith",
5
+ "icon": "ktl-vertical-menu-module_icon_-Ieat",
6
+ "dropdownHeader": "ktl-vertical-menu-module_dropdown-header_77lTy",
7
+ "dropdownList": "ktl-vertical-menu-module_dropdown-list_N3KWV",
8
+ "dropdownItem": "ktl-vertical-menu-module_dropdown-item_XLfp4",
9
+ "dropdownItemActive": "ktl-vertical-menu-module_dropdown-item-active_iBUbj",
10
+ "verticalMenuExpanded": "ktl-vertical-menu-module_vertical-menu-expanded_kFaaI",
11
+ "dropdownListDarkTheme": "ktl-vertical-menu-module_dropdown-list-dark-theme_A1-Bw"
12
+ };
13
+ export { styles as default };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@jetbrains/kotlin-web-site-ui",
3
3
  "description": "UI components for Kotlin web sites development",
4
- "version": "4.2.0-alpha.2",
4
+ "version": "4.2.0-alpha.4",
5
5
  "license": "Apache-2.0",
6
6
  "author": "JetBrains",
7
7
  "files": [
@@ -1,16 +0,0 @@
1
- var styles = {
2
- "overlay": "ktl-dropdown-menu-module_overlay_segRo",
3
- "fadein": "ktl-dropdown-menu-module_fadein_MySnq",
4
- "dropdownMenu": "ktl-dropdown-menu-module_dropdown-menu_tq2uU",
5
- "button": "ktl-dropdown-menu-module_button_OYsuv",
6
- "buttonText": "ktl-dropdown-menu-module_button-text_SJmh-",
7
- "icon": "ktl-dropdown-menu-module_icon_GGhMI",
8
- "dropdownHeader": "ktl-dropdown-menu-module_dropdown-header_fa92T",
9
- "dropdownList": "ktl-dropdown-menu-module_dropdown-list_Ylkvt",
10
- "dropdownItem": "ktl-dropdown-menu-module_dropdown-item_X3tZ-",
11
- "dropdownItemActive": "ktl-dropdown-menu-module_dropdown-item-active_99q9X",
12
- "overlayVisible": "ktl-dropdown-menu-module_overlay-visible_MjwEF",
13
- "dropdownMenuExpanded": "ktl-dropdown-menu-module_dropdown-menu-expanded_EQefy",
14
- "dropdownListDarkTheme": "ktl-dropdown-menu-module_dropdown-list-dark-theme_P60ol"
15
- };
16
- export { styles as default };