@redocly/theme 0.1.27 → 0.1.30

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.
Files changed (132) hide show
  1. package/Button/Button.js +3 -3
  2. package/CodeBlock/CodeBlock.js +1 -1
  3. package/CopyButton/CopyButton.js +17 -1
  4. package/CopyButton/CopyButtonWrapper.js +1 -1
  5. package/Footer/Footer.js +2 -1
  6. package/Footer/FooterColumn.js +1 -1
  7. package/Footer/FooterColumns.d.ts +2 -2
  8. package/Footer/FooterColumns.js +1 -1
  9. package/JsonViewer/JsonViewer.d.ts +2 -0
  10. package/JsonViewer/JsonViewer.js +53 -22
  11. package/Markdown/Admonition.js +1 -1
  12. package/Markdown/CodeSample/CodeSample.js +17 -1
  13. package/Markdown/Heading.js +1 -1
  14. package/Markdown/Mermaid.js +1 -1
  15. package/Markdown/Sup.d.ts +2 -0
  16. package/Markdown/Sup.js +19 -0
  17. package/Markdown/Tabs/Tabs.js +17 -1
  18. package/Markdown/index.d.ts +4 -3
  19. package/Markdown/index.js +4 -3
  20. package/Navbar/MobileNavbarDropdown.d.ts +8 -0
  21. package/Navbar/MobileNavbarDropdown.js +21 -0
  22. package/Navbar/MobileNavbarItem.d.ts +15 -0
  23. package/Navbar/MobileNavbarItem.js +102 -0
  24. package/Navbar/MobileNavbarMenu.d.ts +6 -0
  25. package/Navbar/MobileNavbarMenu.js +32 -0
  26. package/Navbar/MobileNavbarMenuButton.d.ts +4 -0
  27. package/Navbar/MobileNavbarMenuButton.js +19 -0
  28. package/Navbar/Navbar.js +26 -4
  29. package/Navbar/NavbarDropdown.js +1 -1
  30. package/Navbar/NavbarItem.d.ts +9 -3
  31. package/Navbar/NavbarItem.js +9 -9
  32. package/Navbar/NavbarMenu.js +3 -2
  33. package/PageNavigation/NextPageLink.js +4 -4
  34. package/PageNavigation/PreviousPageLink.js +4 -4
  35. package/Panel/PanelComponent.js +18 -2
  36. package/Profile/Profile.d.ts +8 -0
  37. package/Profile/Profile.js +60 -0
  38. package/Profile/index.d.ts +2 -0
  39. package/Profile/index.js +5 -0
  40. package/Search/Autocomplete.js +18 -2
  41. package/Search/utils.js +17 -1
  42. package/Sidebar/SidebarLayout.js +17 -1
  43. package/SourceCode/SourceCode.d.ts +10 -3
  44. package/SourceCode/SourceCode.js +10 -5
  45. package/TableOfContent/TableOfContent.js +13 -5
  46. package/Tooltip/Tooltip.d.ts +5 -4
  47. package/Tooltip/Tooltip.js +43 -21
  48. package/globalStyle.js +2 -2
  49. package/hooks/__tests__/mocks/MockIntersectionObserver.d.ts +15 -0
  50. package/hooks/__tests__/mocks/MockIntersectionObserver.js +39 -0
  51. package/hooks/useActiveHeading.d.ts +1 -1
  52. package/hooks/useActiveHeading.js +86 -21
  53. package/hooks/useActiveSectionId.js +17 -1
  54. package/hooks/useControl.js +17 -1
  55. package/hooks/useMobileMenu.js +17 -1
  56. package/hooks/useNavbarHeight.js +17 -1
  57. package/index.d.ts +1 -0
  58. package/index.js +1 -0
  59. package/package.json +1 -1
  60. package/src/Button/Button.tsx +5 -1
  61. package/src/CodeBlock/CodeBlock.ts +12 -0
  62. package/src/CopyButton/CopyButtonWrapper.tsx +1 -1
  63. package/src/Footer/Footer.tsx +4 -3
  64. package/src/Footer/FooterColumn.tsx +3 -1
  65. package/src/Footer/FooterColumns.tsx +3 -3
  66. package/src/JsonViewer/JsonViewer.tsx +55 -40
  67. package/src/Markdown/Admonition.tsx +1 -1
  68. package/src/Markdown/Heading.tsx +1 -1
  69. package/src/Markdown/Mermaid.tsx +1 -1
  70. package/src/Markdown/Sup.tsx +8 -0
  71. package/src/Markdown/index.ts +4 -3
  72. package/src/Navbar/MobileNavbarDropdown.tsx +37 -0
  73. package/src/Navbar/MobileNavbarItem.tsx +116 -0
  74. package/src/Navbar/MobileNavbarMenu.tsx +106 -0
  75. package/src/Navbar/MobileNavbarMenuButton.tsx +13 -0
  76. package/src/Navbar/Navbar.tsx +11 -3
  77. package/src/Navbar/NavbarDropdown.tsx +1 -1
  78. package/src/Navbar/NavbarItem.tsx +9 -8
  79. package/src/Navbar/NavbarMenu.tsx +9 -4
  80. package/src/PageNavigation/NextPageLink.tsx +3 -3
  81. package/src/PageNavigation/PreviousPageLink.tsx +3 -3
  82. package/src/Profile/Profile.tsx +91 -0
  83. package/src/Profile/index.ts +2 -0
  84. package/src/SourceCode/SourceCode.tsx +32 -5
  85. package/src/TableOfContent/TableOfContent.tsx +14 -4
  86. package/src/Tooltip/Tooltip.tsx +87 -63
  87. package/src/globalStyle.ts +2 -0
  88. package/src/hooks/useActiveHeading.ts +92 -28
  89. package/src/index.ts +1 -0
  90. package/src/types/portal/src/client/app/Sidebar/types.d.ts +2 -1
  91. package/src/types/portal/src/server/constants.d.ts +2 -0
  92. package/src/types/portal/src/server/dev-server/types.d.ts +22 -0
  93. package/src/types/portal/src/server/plugins/markdown/types.d.ts +15 -5
  94. package/src/types/portal/src/server/plugins/portal-config/get-frontmatter-keys-to-resolve.d.ts +2 -0
  95. package/src/types/portal/src/server/plugins/portal-config/types.d.ts +6 -2
  96. package/src/types/portal/src/server/plugins/reference-docs/utils.d.ts +26 -0
  97. package/src/types/portal/src/server/plugins/types.d.ts +29 -12
  98. package/src/types/portal/src/server/store.d.ts +16 -16
  99. package/src/types/portal/src/server/utils/fs.d.ts +2 -1
  100. package/src/types/portal/src/server/utils/index.d.ts +1 -0
  101. package/src/types/portal/src/server/utils/paths.d.ts +4 -0
  102. package/src/types/portal/src/server/utils/rbac.d.ts +15 -0
  103. package/src/types/portal/src/shared/constants.d.ts +7 -0
  104. package/src/types/portal/src/shared/models/config.d.ts +24 -12
  105. package/src/types/portal/src/shared/types.d.ts +17 -4
  106. package/src/types/portal/src/shared/urls.d.ts +1 -1
  107. package/src/types/portal/src/shared/utils.d.ts +2 -0
  108. package/src/ui/Burger.tsx +36 -0
  109. package/src/ui/Flex.tsx +1 -0
  110. package/src/utils/args-typecheck.ts +9 -0
  111. package/src/utils/color.ts +9 -0
  112. package/src/utils/highlight.ts +11 -0
  113. package/src/utils/index.ts +2 -0
  114. package/src/utils/jsonToHtml.ts +171 -59
  115. package/src/utils/theme-helpers.ts +3 -1
  116. package/ui/Burger.d.ts +8 -0
  117. package/ui/Burger.js +23 -0
  118. package/ui/Dropdown.js +17 -1
  119. package/ui/Flex.js +1 -1
  120. package/ui/UniversalLink.js +17 -1
  121. package/utils/args-typecheck.d.ts +2 -0
  122. package/utils/args-typecheck.js +13 -0
  123. package/utils/color.d.ts +2 -0
  124. package/utils/color.js +12 -0
  125. package/utils/highlight.d.ts +1 -0
  126. package/utils/highlight.js +12 -1
  127. package/utils/index.d.ts +2 -0
  128. package/utils/index.js +2 -0
  129. package/utils/jsonToHtml.d.ts +4 -1
  130. package/utils/jsonToHtml.js +287 -83
  131. package/utils/media-css.js +40 -3
  132. package/utils/theme-helpers.js +59 -10
@@ -3,9 +3,10 @@ import styled from 'styled-components';
3
3
 
4
4
  import { NavbarItem } from '@theme/Navbar/NavbarItem';
5
5
  import type { ResolvedConfigLinks, ResolvedNavItem } from '@theme/types/portal';
6
+ import { isPrimitive, isEmptyArray } from '@theme/utils';
6
7
 
7
8
  export function NavbarMenu({ menuItems }: { menuItems: ResolvedConfigLinks }): JSX.Element | null {
8
- if (!menuItems?.length) {
9
+ if (isPrimitive(menuItems) || isEmptyArray(menuItems)) {
9
10
  return null;
10
11
  }
11
12
 
@@ -22,11 +23,15 @@ export function NavbarMenu({ menuItems }: { menuItems: ResolvedConfigLinks }): J
22
23
 
23
24
  const NavItemsContainer = styled.ul`
24
25
  list-style: none;
25
- margin: 0;
26
+ margin: 0 7px;
26
27
  padding: 0;
27
- display: block;
28
+ display: none;
29
+ flex-wrap: nowrap;
30
+ align-items: center;
31
+ justify-content: flex-end;
32
+ flex: 1;
28
33
 
29
34
  ${({ theme }) => theme.mediaQueries.medium} {
30
- display: block;
35
+ display: flex;
31
36
  }
32
37
  `;
@@ -12,13 +12,13 @@ interface NextPageType {
12
12
 
13
13
  export function NextPageLink(): JSX.Element {
14
14
  const { nextPage }: NextPageType = useSidebarSiblingsData() || {};
15
- const { themeSettings } = useThemeSettings(DEFAULT_THEME_NAME);
15
+ const { navigation } = useThemeSettings(DEFAULT_THEME_NAME);
16
16
 
17
- if (!nextPage || themeSettings?.navigation?.hide) {
17
+ if (!nextPage || navigation?.hide) {
18
18
  return <div>&nbsp;</div>;
19
19
  }
20
20
 
21
- const label = themeSettings?.navigation?.nextPageLink?.label || `Next to ${nextPage.label}`;
21
+ const label = navigation?.nextPageLink?.label || `Next to ${nextPage.label}`;
22
22
 
23
23
  return (
24
24
  <StyledButton
@@ -12,13 +12,13 @@ interface PreviousPageType {
12
12
 
13
13
  export function PreviousPageLink(): JSX.Element {
14
14
  const { prevPage }: PreviousPageType = useSidebarSiblingsData() || {};
15
- const { themeSettings } = useThemeSettings(DEFAULT_THEME_NAME);
15
+ const { navigation } = useThemeSettings(DEFAULT_THEME_NAME);
16
16
 
17
- if (!prevPage || themeSettings?.navigation?.hide) {
17
+ if (!prevPage || navigation?.hide) {
18
18
  return <div>&nbsp;</div>;
19
19
  }
20
20
 
21
- const label = themeSettings?.navigation?.nextPageLink?.label || `Back to ${prevPage.label}`;
21
+ const label = navigation?.nextPageLink?.label || `Back to ${prevPage.label}`;
22
22
 
23
23
  return (
24
24
  <StyledButton
@@ -0,0 +1,91 @@
1
+ import React, { memo } from 'react';
2
+ import styled, { css } from 'styled-components';
3
+
4
+ import { getRandomColor } from '@theme/utils';
5
+
6
+ export interface ProfileProps {
7
+ name: string;
8
+ imageUrl?: string;
9
+ color?: string;
10
+ onClick?: () => void;
11
+ }
12
+
13
+ const RANDOM_BG_COLOR: string = getRandomColor();
14
+
15
+ function ProfileComponent({ name, imageUrl, onClick, color }: ProfileProps): JSX.Element {
16
+ if (imageUrl) {
17
+ return (
18
+ <ProfileWrapper onClick={onClick}>
19
+ <StyledUserName data-cy="user-name" color={color}>
20
+ {name}
21
+ </StyledUserName>
22
+ {imageUrl && (
23
+ <AvatarWrapper>
24
+ <img data-cy="user-avatar" src={imageUrl} alt="profile" />
25
+ </AvatarWrapper>
26
+ )}
27
+ </ProfileWrapper>
28
+ );
29
+ }
30
+
31
+ const avatarLetters = `${name.charAt(0).toUpperCase()}${
32
+ name.split(' ')[1]?.charAt(0).toUpperCase() || ''
33
+ }`;
34
+
35
+ return (
36
+ <ProfileWrapper onClick={onClick}>
37
+ <StyledUserName data-cy="user-name" color={color}>
38
+ {name}
39
+ </StyledUserName>
40
+ <AvatarWrapper background={RANDOM_BG_COLOR}>
41
+ <span>{avatarLetters}</span>
42
+ </AvatarWrapper>
43
+ </ProfileWrapper>
44
+ );
45
+ }
46
+
47
+ export const Profile = memo<ProfileProps>(ProfileComponent);
48
+
49
+ const ProfileWrapper = styled.div`
50
+ display: flex;
51
+ align-items: center;
52
+ cursor: pointer;
53
+ width: auto;
54
+ `;
55
+
56
+ const StyledUserName = styled.span`
57
+ color: ${({ color }) => color || 'var(--color-content)'};
58
+ `;
59
+
60
+ const AvatarWrapper = styled.div<{ background?: string }>`
61
+ width: 40px;
62
+ height: 40px;
63
+ display: flex;
64
+ overflow: hidden;
65
+ position: relative;
66
+ font-size: 1.25rem;
67
+ align-items: center;
68
+ flex-shrink: 0;
69
+ line-height: 1;
70
+ user-select: none;
71
+ border-radius: 50%;
72
+ justify-content: center;
73
+ margin-left: 16px;
74
+
75
+ ${({ background }) => css`
76
+ background-color: ${background};
77
+ span {
78
+ color: ${background};
79
+ filter: invert(100%);
80
+ }
81
+ `}
82
+
83
+ & > img {
84
+ color: transparent;
85
+ width: 100%;
86
+ height: 100%;
87
+ object-fit: cover;
88
+ text-align: center;
89
+ text-indent: 10000px;
90
+ }
91
+ `;
@@ -0,0 +1,2 @@
1
+ export { Profile } from './Profile';
2
+ export type { ProfileProps } from './Profile';
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
 
3
- import { highlight } from '@theme/utils';
3
+ import { highlight, addLineNumbers } from '@theme/utils';
4
4
  import {
5
5
  SampleControls,
6
6
  SampleControlsWrap,
@@ -8,7 +8,12 @@ import {
8
8
  } from '@theme/SamplesPanelControls';
9
9
  import { CopyButtonWrapper } from '@theme/CopyButton';
10
10
 
11
- export interface SourceCodeProps {
11
+ interface CommonCodeProps {
12
+ withLineNumbers?: boolean;
13
+ startLineNumber?: number;
14
+ }
15
+
16
+ export interface SourceCodeProps extends CommonCodeProps {
12
17
  lang: string;
13
18
  source?: string;
14
19
  externalSource?: ExternalSource;
@@ -16,6 +21,10 @@ export interface SourceCodeProps {
16
21
  dataTestId?: string;
17
22
  }
18
23
 
24
+ interface CodeProps
25
+ extends Required<Pick<SourceCodeProps, 'lang' | 'source' | 'dataTestId'>>,
26
+ CommonCodeProps {}
27
+
19
28
  export interface Sample {
20
29
  lang: string;
21
30
  label?: string;
@@ -37,10 +46,18 @@ export function Code({
37
46
  source,
38
47
  lang,
39
48
  dataTestId,
40
- }: Required<Omit<SourceCodeProps, 'externalSource' | 'withCopyButton'>>): JSX.Element {
49
+ withLineNumbers,
50
+ startLineNumber,
51
+ }: CodeProps): JSX.Element {
52
+ const highlightedCode = highlight(source, lang);
53
+
41
54
  return (
42
55
  <PreformattedCodeBlock
43
- dangerouslySetInnerHTML={{ __html: highlight(source, lang) }}
56
+ dangerouslySetInnerHTML={{
57
+ __html: withLineNumbers
58
+ ? addLineNumbers(highlightedCode, startLineNumber)
59
+ : highlightedCode,
60
+ }}
44
61
  data-cy={dataTestId}
45
62
  />
46
63
  );
@@ -52,6 +69,8 @@ export function SourceCode({
52
69
  externalSource,
53
70
  withCopyButton,
54
71
  dataTestId = 'source-code',
72
+ withLineNumbers,
73
+ startLineNumber,
55
74
  }: SourceCodeProps): JSX.Element {
56
75
  const _source = source || externalSource?.sample?.get?.(externalSource) || '';
57
76
  if (withCopyButton) {
@@ -60,7 +79,13 @@ export function SourceCode({
60
79
  {({ renderCopyButton }) => (
61
80
  <SampleControlsWrap>
62
81
  <SampleControls data-cy="copy-button">{renderCopyButton()}</SampleControls>
63
- <Code lang={lang} source={_source} dataTestId={dataTestId} />
82
+ <Code
83
+ lang={lang}
84
+ source={_source}
85
+ withLineNumbers={withLineNumbers}
86
+ startLineNumber={startLineNumber}
87
+ dataTestId={dataTestId}
88
+ />
64
89
  </SampleControlsWrap>
65
90
  )}
66
91
  </CopyButtonWrapper>
@@ -72,6 +97,8 @@ export function SourceCode({
72
97
  dataTestId={dataTestId}
73
98
  lang={lang}
74
99
  source={_source}
100
+ withLineNumbers={withLineNumbers}
101
+ startLineNumber={startLineNumber}
75
102
  data-component-name="SourceCode/SourceCode"
76
103
  />
77
104
  );
@@ -18,10 +18,20 @@ export function TableOfContent(props: TableOfContentProps): JSX.Element | null {
18
18
 
19
19
  const sidebar = useRef<HTMLDivElement | null>(null);
20
20
  useFullHeight(sidebar);
21
- const activeHeadingId = useActiveHeading(contentWrapper);
22
- const { themeSettings } = useThemeSettings(DEFAULT_THEME_NAME);
21
+ const { toc } = useThemeSettings(DEFAULT_THEME_NAME);
23
22
 
24
- if (themeSettings?.toc?.hide) {
23
+ const getDisplayedHeaderIds = () => {
24
+ if (!headings) {
25
+ return [];
26
+ }
27
+ return headings
28
+ .filter((header) => header && tocMaxDepth >= header.depth)
29
+ .map((header) => header?.id);
30
+ };
31
+
32
+ const activeHeadingId = useActiveHeading(contentWrapper, getDisplayedHeaderIds());
33
+
34
+ if (toc?.hide) {
25
35
  return null;
26
36
  }
27
37
  if (headings && headings.length === 1 && (!headings[0] || headings[0].depth === 1)) {
@@ -36,7 +46,7 @@ export function TableOfContent(props: TableOfContentProps): JSX.Element | null {
36
46
  {headings && (
37
47
  <TableOfContentMenu data-component-name="TableOfContent/TableOfContent">
38
48
  <TableOfContentItems ref={sidebar}>
39
- <TocHeader>{themeSettings?.toc?.header?.label || 'On this page'}</TocHeader>
49
+ <TocHeader>{toc?.header?.label || 'On this page'}</TocHeader>
40
50
  {headings.map((heading: MdHeading | null, idx: number) => {
41
51
  // TODO: not sure about !heading
42
52
  if (!heading) {
@@ -1,12 +1,13 @@
1
- import type { PropsWithChildren } from 'react';
2
- import React, { useCallback, useEffect, memo } from 'react';
1
+ import type { PropsWithChildren, ReactNode } from 'react';
2
+ import React, { useEffect, memo } from 'react';
3
3
  import styled, { css } from 'styled-components';
4
4
 
5
5
  import { useControl } from '@theme/hooks';
6
6
 
7
7
  export interface TooltipProps {
8
- tip: string;
9
- open?: boolean;
8
+ tip: string | ReactNode;
9
+ isOpen?: boolean;
10
+ withArrow?: boolean;
10
11
  placement?: 'top' | 'bottom' | 'left' | 'right';
11
12
  className?: string;
12
13
  width?: string;
@@ -15,46 +16,48 @@ export interface TooltipProps {
15
16
 
16
17
  export function TooltipComponent({
17
18
  children,
18
- open,
19
+ isOpen,
19
20
  tip,
21
+ withArrow = true,
20
22
  placement = 'top',
21
23
  className = 'default',
22
24
  width,
23
- dataTestId = tip,
25
+ dataTestId,
24
26
  }: PropsWithChildren<TooltipProps>): JSX.Element {
25
- const { isOpened, handleOpen, handleClose } = useControl(open);
27
+ const { isOpened, handleOpen, handleClose } = useControl(isOpen);
26
28
 
27
- const isControlled = open !== undefined;
29
+ const isControlled = isOpen !== undefined;
28
30
 
29
31
  useEffect(() => {
30
32
  if (isControlled) {
31
- if (open) {
33
+ if (isOpen) {
32
34
  handleOpen();
33
35
  } else {
34
36
  handleClose();
35
37
  }
36
38
  }
37
- }, [open, isControlled, handleOpen, handleClose]);
39
+ }, [isOpen, isControlled, handleOpen, handleClose]);
38
40
 
39
- const handleEnter = useCallback(() => {
40
- handleOpen();
41
- }, [handleOpen]);
42
-
43
- const handleLeave = useCallback(() => {
44
- handleClose();
45
- }, [handleClose]);
41
+ const controllers = !isControlled && {
42
+ onMouseEnter: handleOpen,
43
+ onMouseLeave: handleClose,
44
+ onClick: handleClose,
45
+ };
46
46
 
47
47
  return (
48
48
  <TooltipWrapper
49
- onMouseEnter={isControlled ? undefined : handleEnter}
50
- onMouseLeave={isControlled ? undefined : handleLeave}
51
- onClick={isControlled ? undefined : handleLeave}
49
+ {...controllers}
52
50
  className={`tooltip-${className}`}
53
51
  data-component-name="Tooltip/Tooltip"
54
52
  >
55
53
  {children}
56
54
  {isOpened && (
57
- <TooltipBody data-cy={dataTestId} placement={placement} width={width}>
55
+ <TooltipBody
56
+ data-cy={dataTestId || (typeof tip === 'string' ? tip : '')}
57
+ placement={placement}
58
+ width={width}
59
+ withArrow={withArrow}
60
+ >
58
61
  {tip}
59
62
  </TooltipBody>
60
63
  )}
@@ -65,69 +68,85 @@ export function TooltipComponent({
65
68
  export const Tooltip = memo<PropsWithChildren<TooltipProps>>(TooltipComponent);
66
69
 
67
70
  const PLACEMENTS = {
68
- top: css`
71
+ top: css<Pick<TooltipProps, 'withArrow'>>`
69
72
  top: 0;
70
73
  left: 50%;
71
74
  transform: translate(-50%, -99%);
72
75
  margin-top: -10px;
73
76
 
74
- &::after {
75
- border-left: 5px solid transparent;
76
- border-right: 5px solid transparent;
77
- border-top-width: 6px;
78
- border-top-style: solid;
79
- bottom: 0;
80
- left: 50%;
81
- transform: translate(-50%, 99%);
82
- }
77
+ ${({ withArrow }) =>
78
+ withArrow &&
79
+ css`
80
+ &::after {
81
+ border-left: 5px solid transparent;
82
+ border-right: 5px solid transparent;
83
+ border-top-width: 6px;
84
+ border-top-style: solid;
85
+ bottom: 0;
86
+ left: 50%;
87
+ transform: translate(-50%, 99%);
88
+ }
89
+ `}
83
90
  `,
84
- bottom: css`
91
+ bottom: css<Pick<TooltipProps, 'withArrow'>>`
85
92
  bottom: 0;
86
93
  left: 50%;
87
94
  transform: translate(-50%, 99%);
88
95
  margin-bottom: -10px;
89
96
 
90
- &::after {
91
- border-left: 5px solid transparent;
92
- border-right: 5px solid transparent;
93
- border-bottom-width: 6px;
94
- border-bottom-style: solid;
95
- top: 0;
96
- left: 50%;
97
- transform: translate(-50%, -99%);
98
- }
97
+ ${({ withArrow }) =>
98
+ withArrow &&
99
+ css`
100
+ &::after {
101
+ border-left: 5px solid transparent;
102
+ border-right: 5px solid transparent;
103
+ border-bottom-width: 6px;
104
+ border-bottom-style: solid;
105
+ top: 0;
106
+ left: 50%;
107
+ transform: translate(-50%, -99%);
108
+ }
109
+ `}
99
110
  `,
100
- left: css`
111
+ left: css<Pick<TooltipProps, 'withArrow'>>`
101
112
  top: 50%;
102
113
  left: 0;
103
114
  transform: translate(-100%, -50%);
104
115
  margin-left: -10px;
105
116
 
106
- &::after {
107
- border-top: 5px solid transparent;
108
- border-bottom: 5px solid transparent;
109
- border-left-width: 6px;
110
- border-left-style: solid;
111
- top: 50%;
112
- right: 0;
113
- transform: translate(99%, -50%);
114
- }
117
+ ${({ withArrow }) =>
118
+ withArrow &&
119
+ css`
120
+ &::after {
121
+ border-top: 5px solid transparent;
122
+ border-bottom: 5px solid transparent;
123
+ border-left-width: 6px;
124
+ border-left-style: solid;
125
+ top: 50%;
126
+ right: 0;
127
+ transform: translate(99%, -50%);
128
+ }
129
+ `}
115
130
  `,
116
- right: css`
131
+ right: css<Pick<TooltipProps, 'withArrow'>>`
117
132
  top: 50%;
118
133
  right: 0;
119
134
  transform: translate(100%, -50%);
120
135
  margin-right: -10px;
121
136
 
122
- &::after {
123
- border-top: 5px solid transparent;
124
- border-bottom: 5px solid transparent;
125
- border-right-width: 6px;
126
- border-right-style: solid;
127
- top: 50%;
128
- left: 0;
129
- transform: translate(-99%, -50%);
130
- }
137
+ ${({ withArrow }) =>
138
+ withArrow &&
139
+ css`
140
+ &::after {
141
+ border-top: 5px solid transparent;
142
+ border-bottom: 5px solid transparent;
143
+ border-right-width: 6px;
144
+ border-right-style: solid;
145
+ top: 50%;
146
+ left: 0;
147
+ transform: translate(-99%, -50%);
148
+ }
149
+ `}
131
150
  `,
132
151
  };
133
152
 
@@ -136,7 +155,9 @@ const TooltipWrapper = styled.div`
136
155
  display: inline-block;
137
156
  `;
138
157
 
139
- const TooltipBody = styled.span<Pick<Required<TooltipProps>, 'placement'> & { width?: string }>`
158
+ const TooltipBody = styled.span<
159
+ Pick<Required<TooltipProps>, 'placement' | 'withArrow'> & { width?: string }
160
+ >`
140
161
  display: inline-block;
141
162
 
142
163
  position: absolute;
@@ -168,5 +189,8 @@ const TooltipBody = styled.span<Pick<Required<TooltipProps>, 'placement'> & { wi
168
189
  box-shadow: rgb(0 0 0 / 25%) 0 2px 4px;
169
190
 
170
191
  width: ${({ width }) => width || '120px'};
171
- ${({ placement }) => `${PLACEMENTS[placement]};`}
192
+ ${({ placement }) =>
193
+ css`
194
+ ${PLACEMENTS[placement]};
195
+ `}
172
196
  `;
@@ -299,6 +299,7 @@ const buttons = css`
299
299
  * @tokens Button borders
300
300
  */
301
301
  --button-border-radius: var(--global-border-radius); // @presenter BorderRadius
302
+ --button-margin: 5px; // @presenter Margin
302
303
  --button-box-shadow: none; // @presenter Shadow
303
304
  --button-active-box-shadow: 0px 0px 12px 0px rgba(0, 0, 0, 0.1); // @presenter Shadow
304
305
 
@@ -751,6 +752,7 @@ const navbar = css`
751
752
  --navbar-item-font-weight: var(--font-weight-bold); // @presenter FontWeight
752
753
  --navbar-item-active-background-color: #1b75fa; // @presenter Color
753
754
  --navbar-item-active-text-color: var(--navbar-text-color); // @presenter Color
755
+ --navbar-item-separator-line-color: var(--sidebar-separator-line-color); // @presenter Color
754
756
  --navbar-item-active-text-decoration: none;
755
757
 
756
758
  // @tokens End
@@ -1,46 +1,110 @@
1
- import { useState, useMemo, useCallback, useEffect } from 'react';
1
+ import { useState, useEffect, useRef } from 'react';
2
+ import { useHistory } from 'react-router-dom';
2
3
 
3
4
  export type UseActiveHeadingReturnType = string | undefined;
4
5
 
5
- export function useActiveHeading(contentElement: Element | null): UseActiveHeadingReturnType {
6
+ type HeadingEntry = {
7
+ [key: string]: IntersectionObserverEntry;
8
+ };
9
+
10
+ export function useActiveHeading(
11
+ contentElement: HTMLDivElement | null,
12
+ displayedHeaders: Array<string | undefined>,
13
+ ): UseActiveHeadingReturnType {
6
14
  const [heading, setHeading] = useState<string | undefined>(undefined);
15
+ const [headingElements, setHeadingElements] = useState<HTMLElement[]>([]);
16
+ const headingElementsRef = useRef<HeadingEntry>({});
7
17
 
8
- const headings: NodeListOf<HTMLElement> | null = useMemo(
9
- () => contentElement && contentElement.querySelectorAll<HTMLElement>('.heading-anchor'),
10
- [contentElement],
11
- );
18
+ const history = useHistory();
12
19
 
13
- const handler = useCallback(
14
- // throttle(
15
- () => {
16
- if (!headings) {
17
- return;
18
- }
20
+ const getVisibleHeadings = () => {
21
+ const visibleHeadings: IntersectionObserverEntry[] = [];
22
+
23
+ for (const key in headingElementsRef.current) {
24
+ const headingElement = headingElementsRef.current[key];
19
25
 
20
- for (let i = 0; i < headings.length; i++) {
21
- if (headings[i].offsetTop > window.scrollY) {
22
- const newHeading = i === 0 ? undefined : headings[i - 1].getAttribute('id') || undefined;
23
- setHeading(newHeading);
24
- return;
25
- }
26
+ if (headingElement.isIntersecting) {
27
+ visibleHeadings.push(headingElement);
26
28
  }
27
- },
28
- [headings],
29
- );
29
+ }
30
+
31
+ return visibleHeadings;
32
+ };
33
+
34
+ const getIndexFromId = (id: string) => {
35
+ return headingElements.findIndex((item) => item.id === id);
36
+ };
37
+
38
+ const findHeaders = (allContent: HTMLDivElement) => {
39
+ const allHeaders = allContent.querySelectorAll<HTMLElement>('.heading-anchor');
40
+ return Array.from(allHeaders);
41
+ };
42
+
43
+ const intersectionCallback = (headings: IntersectionObserverEntry[]) => {
44
+ headingElementsRef.current = headings.reduce(
45
+ (map: HeadingEntry, headingElement: IntersectionObserverEntry) => {
46
+ map[headingElement.target.id] = headingElement;
47
+ return map;
48
+ },
49
+ headingElementsRef.current,
50
+ );
51
+
52
+ const totalHeight = window.scrollY + window.innerHeight;
53
+ // handle bottom of the page
54
+ if (totalHeight >= document.body.scrollHeight) {
55
+ const newHeading = headingElements[headingElements?.length - 1]?.id || undefined;
56
+ setHeading(newHeading);
57
+ return;
58
+ }
59
+
60
+ const visibleHeadings = getVisibleHeadings();
61
+ if (!visibleHeadings.length) {
62
+ return;
63
+ }
64
+
65
+ if (visibleHeadings.length === 1) {
66
+ setHeading(visibleHeadings[0].target.id);
67
+ return;
68
+ }
69
+
70
+ visibleHeadings.sort((a, b) => {
71
+ return getIndexFromId(a.target.id) - getIndexFromId(b.target.id);
72
+ });
73
+ setHeading(visibleHeadings[0].target.id);
74
+ };
30
75
 
31
76
  useEffect(() => {
32
- if (typeof window === 'undefined' || !headings || !headings.length) {
33
- return undefined;
77
+ if (!contentElement) {
78
+ return;
34
79
  }
80
+ setHeadingElements(findHeaders(contentElement));
35
81
 
36
- window.addEventListener('scroll', handler, {
37
- capture: false,
82
+ const unlisten = history.listen(() => {
83
+ setHeadingElements(findHeaders(contentElement));
38
84
  });
39
85
 
40
- handler();
86
+ return () => unlisten();
87
+ }, [contentElement]);
88
+
89
+ useEffect(() => {
90
+ if (!headingElements?.length) {
91
+ return;
92
+ }
93
+ headingElementsRef.current = {};
94
+
95
+ // Bottom rootMargin -30% changes part of the view where IntersectionObserver starts to detect headers
96
+ const observer = new IntersectionObserver(intersectionCallback, {
97
+ rootMargin: '0px 0px -30% 0px',
98
+ threshold: 1,
99
+ });
100
+ headingElements?.forEach((element) => {
101
+ if (displayedHeaders.includes(element.id)) {
102
+ observer.observe(element);
103
+ }
104
+ });
41
105
 
42
- return () => window.removeEventListener('scroll', handler);
43
- }, [handler, headings]);
106
+ return () => observer.disconnect();
107
+ }, [headingElements, displayedHeaders]);
44
108
 
45
109
  return heading;
46
110
  }
package/src/index.ts CHANGED
@@ -14,3 +14,4 @@ export * from './utils';
14
14
  export * from './globalStyle';
15
15
  export * from './OperationBadge';
16
16
  export * from './TableOfContent';
17
+ export * from './Profile';
@@ -1,3 +1,4 @@
1
+ import { PERMISSION_PROP_NAME } from '../../../shared/constants';
1
2
  import { MenuStyle } from '../../../shared/types';
2
3
  export interface SidebarNavItem {
3
4
  label?: string;
@@ -18,7 +19,7 @@ export interface SidebarNavItem {
18
19
  target?: string;
19
20
  external?: boolean;
20
21
  items?: SidebarNavItem[];
21
- permission?: string;
22
+ [PERMISSION_PROP_NAME]?: string;
22
23
  menuStyle?: MenuStyle;
23
24
  separatorLine?: boolean;
24
25
  version?: string;