@redocly/theme 0.6.4 → 0.7.0

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 (86) hide show
  1. package/lib/Feedback/Comment.d.ts +3 -0
  2. package/lib/Feedback/Comment.js +80 -0
  3. package/lib/Feedback/Rating.d.ts +3 -0
  4. package/lib/Feedback/Rating.js +82 -0
  5. package/lib/Feedback/Reasons.d.ts +3 -0
  6. package/lib/Feedback/Reasons.js +85 -0
  7. package/lib/Feedback/Sentiment.d.ts +3 -0
  8. package/lib/Feedback/Sentiment.js +79 -0
  9. package/lib/Feedback/Thumbs.d.ts +7 -0
  10. package/lib/Feedback/Thumbs.js +79 -0
  11. package/lib/Feedback/index.d.ts +5 -0
  12. package/lib/Feedback/index.js +27 -0
  13. package/lib/Feedback/types.d.ts +71 -0
  14. package/lib/Feedback/types.js +3 -0
  15. package/lib/Markdown/MarkdownLayout.d.ts +2 -1
  16. package/lib/Markdown/MarkdownLayout.js +8 -2
  17. package/lib/Markdown/Tabs/Tab.js +11 -5
  18. package/lib/Markdown/Tabs/Tabs.js +14 -5
  19. package/lib/Navbar/Navbar.js +6 -3
  20. package/lib/Pages/Forbidden.d.ts +2 -0
  21. package/lib/Pages/Forbidden.js +39 -0
  22. package/lib/Pages/NotFound.d.ts +2 -0
  23. package/lib/Pages/NotFound.js +39 -0
  24. package/lib/Pages/index.d.ts +1 -0
  25. package/lib/Pages/index.js +18 -0
  26. package/lib/Profile/LoginLink.d.ts +5 -0
  27. package/lib/Profile/LoginLink.js +30 -0
  28. package/lib/Profile/Profile.js +3 -1
  29. package/lib/Profile/UserProfile.d.ts +13 -0
  30. package/lib/Profile/UserProfile.js +82 -0
  31. package/lib/Profile/index.d.ts +4 -0
  32. package/lib/Profile/index.js +5 -1
  33. package/lib/Search/Autocomplete.d.ts +4 -1
  34. package/lib/Search/Autocomplete.js +19 -3
  35. package/lib/Search/ClearIcon.js +1 -1
  36. package/lib/Search/Input.js +1 -1
  37. package/lib/Search/Search.js +6 -1
  38. package/lib/Search/SearchIcon.js +1 -1
  39. package/lib/Search/ShortcutKey.d.ts +7 -0
  40. package/lib/Search/ShortcutKey.js +35 -0
  41. package/lib/config.d.ts +61 -0
  42. package/lib/config.js +19 -0
  43. package/lib/globalStyle.js +62 -0
  44. package/lib/index.d.ts +2 -0
  45. package/lib/index.js +2 -0
  46. package/lib/mocks/Link.js +1 -1
  47. package/lib/mocks/hooks/index.js +11 -1
  48. package/lib/mocks/search.js +18 -5
  49. package/lib/ui/Box.d.ts +2 -2
  50. package/lib/ui/Box.js +1 -0
  51. package/lib/ui/darkColors.js +5 -0
  52. package/package.json +9 -5
  53. package/src/Feedback/Comment.tsx +64 -0
  54. package/src/Feedback/Rating.tsx +107 -0
  55. package/src/Feedback/Reasons.tsx +81 -0
  56. package/src/Feedback/Sentiment.tsx +75 -0
  57. package/src/Feedback/Thumbs.tsx +116 -0
  58. package/src/Feedback/index.ts +5 -0
  59. package/src/Feedback/types.ts +63 -0
  60. package/src/Markdown/MarkdownLayout.tsx +10 -1
  61. package/src/Markdown/Tabs/Tab.tsx +11 -5
  62. package/src/Markdown/Tabs/Tabs.tsx +14 -5
  63. package/src/Navbar/Navbar.tsx +8 -3
  64. package/src/Pages/Forbidden.tsx +42 -0
  65. package/src/Pages/NotFound.tsx +42 -0
  66. package/src/Pages/index.ts +1 -0
  67. package/src/Profile/LoginLink.tsx +29 -0
  68. package/src/Profile/Profile.tsx +3 -1
  69. package/src/Profile/UserProfile.tsx +101 -0
  70. package/src/Profile/index.ts +4 -0
  71. package/src/Search/Autocomplete.tsx +26 -2
  72. package/src/Search/ClearIcon.tsx +1 -1
  73. package/src/Search/Input.tsx +1 -1
  74. package/src/Search/Search.tsx +3 -0
  75. package/src/Search/SearchIcon.tsx +1 -1
  76. package/src/Search/ShortcutKey.tsx +35 -0
  77. package/src/config.ts +23 -0
  78. package/src/globalStyle.ts +64 -0
  79. package/src/index.ts +2 -0
  80. package/src/mocks/Link.tsx +2 -1
  81. package/src/mocks/hooks/index.ts +11 -1
  82. package/src/mocks/search.ts +20 -5
  83. package/src/settings.yaml +2 -0
  84. package/src/types/portal/index.d.ts +1 -1
  85. package/src/ui/Box.tsx +5 -2
  86. package/src/ui/darkColors.tsx +5 -0
@@ -0,0 +1,116 @@
1
+ import * as React from 'react';
2
+ import styled from 'styled-components';
3
+
4
+ export const ThumbUp = ({ text }: { text?: string }) => (
5
+ <Wrapper style={{ alignItems: 'normal' }}>
6
+ <Icon>
7
+ <svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" viewBox="0 0 512 512">
8
+ <g>
9
+ <g>
10
+ <g>
11
+ <path
12
+ d="M495.736,290.773C509.397,282.317,512,269.397,512,260.796c0-22.4-18.253-47.462-42.667-47.462H349.918
13
+ c-4.284-0.051-25.651-1.51-25.651-25.6c0-4.71-3.814-8.533-8.533-8.533s-8.533,3.823-8.533,8.533
14
+ c0,33.749,27.913,42.667,42.667,42.667h119.467c14.182,0,25.6,16.631,25.6,30.396c0,4.437,0,17.946-26.53,20.855
15
+ c-4.506,0.495-7.834,4.42-7.586,8.951c0.239,4.523,3.985,8.064,8.516,8.064c14.114,0,25.6,11.486,25.6,25.6
16
+ s-11.486,25.6-25.6,25.6h-17.067c-4.719,0-8.533,3.823-8.533,8.533s3.814,8.533,8.533,8.533c14.114,0,25.6,11.486,25.6,25.6
17
+ s-11.486,25.6-25.6,25.6h-25.6c-4.719,0-8.533,3.823-8.533,8.533s3.814,8.533,8.533,8.533c8.934,0,17.067,8.132,17.067,17.067
18
+ c0,8.61-8.448,17.067-17.067,17.067h-128c-35.627,0-48.444-7.074-63.292-15.258c-12.553-6.921-26.786-14.763-54.963-18.79
19
+ c-4.668-0.674-8.994,2.577-9.66,7.236c-0.666,4.668,2.569,8.994,7.236,9.66c25.105,3.584,37.325,10.325,49.152,16.845
20
+ c15.497,8.542,31.505,17.374,71.526,17.374h128c17.869,0,34.133-16.273,34.133-34.133c0-6.229-1.775-12.134-4.83-17.229
21
+ c21.794-1.877,38.963-20.224,38.963-42.505c0-10.829-4.062-20.736-10.735-28.271C500.42,358.212,512,342.571,512,324.267
22
+ C512,310.699,505.634,298.59,495.736,290.773z"
23
+ />
24
+ <path
25
+ d="M76.8,443.733c9.412,0,17.067-7.654,17.067-17.067S86.212,409.6,76.8,409.6c-9.412,0-17.067,7.654-17.067,17.067
26
+ S67.388,443.733,76.8,443.733z"
27
+ />
28
+ <path
29
+ d="M179.2,247.467c25.353,0,57.429-28.297,74.3-45.167c36.634-36.634,36.634-82.167,36.634-151.1
30
+ c0-5.342,3.191-8.533,8.533-8.533c29.508,0,42.667,13.158,42.667,42.667v102.4c0,4.71,3.814,8.533,8.533,8.533
31
+ s8.533-3.823,8.533-8.533v-102.4c0-39.083-20.659-59.733-59.733-59.733c-14.831,0-25.6,10.769-25.6,25.6
32
+ c0,66.978,0,107.401-31.633,139.034C216.661,215.006,192.811,230.4,179.2,230.4c-4.719,0-8.533,3.823-8.533,8.533
33
+ S174.481,247.467,179.2,247.467z"
34
+ />
35
+ <path
36
+ d="M145.067,213.333H8.533c-4.719,0-8.533,3.823-8.533,8.533v256c0,4.71,3.814,8.533,8.533,8.533h136.533
37
+ c4.719,0,8.533-3.823,8.533-8.533v-256C153.6,217.156,149.786,213.333,145.067,213.333z M136.533,469.333H17.067V230.4h119.467
38
+ V469.333z"
39
+ />
40
+ </g>
41
+ </g>
42
+ </g>
43
+ </svg>
44
+ </Icon>
45
+ <span>{text || 'Yes'}</span>
46
+ </Wrapper>
47
+ );
48
+
49
+ export const ThumbDown = ({ text }: { text?: string }) => (
50
+ <Wrapper style={{ alignItems: 'end' }}>
51
+ <Icon>
52
+ <svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" viewBox="0 0 512 512">
53
+ <g>
54
+ <g>
55
+ <g>
56
+ <path
57
+ d="M76.8,247.467c9.412,0,17.067-7.654,17.067-17.067c0-9.412-7.654-17.067-17.067-17.067
58
+ c-9.412,0-17.067,7.654-17.067,17.067C59.733,239.812,67.388,247.467,76.8,247.467z"
59
+ />
60
+ <path
61
+ d="M495.736,221.227C505.634,213.41,512,201.301,512,187.733c0-18.295-11.58-33.946-27.802-39.996
62
+ c6.673-7.535,10.735-17.434,10.735-28.271c0-22.281-17.169-40.627-38.963-42.505c3.055-5.094,4.83-10.999,4.83-17.229
63
+ c0-17.86-16.265-34.133-34.133-34.133h-128c-40.021,0-56.03,8.832-71.526,17.374c-11.827,6.519-24.047,13.261-49.152,16.845
64
+ c-4.668,0.666-7.902,4.992-7.236,9.66c0.666,4.659,4.949,7.885,9.66,7.236c28.177-4.028,42.411-11.87,54.963-18.79
65
+ c14.848-8.183,27.665-15.258,63.292-15.258h128c8.619,0,17.067,8.456,17.067,17.067c0,8.934-8.132,17.067-17.067,17.067
66
+ c-4.719,0-8.533,3.823-8.533,8.533s3.814,8.533,8.533,8.533h25.6c14.114,0,25.6,11.486,25.6,25.6s-11.486,25.6-25.6,25.6
67
+ c-4.719,0-8.533,3.823-8.533,8.533c0,4.71,3.814,8.533,8.533,8.533h17.067c14.114,0,25.6,11.486,25.6,25.6
68
+ s-11.486,25.6-25.6,25.6c-4.531,0-8.277,3.541-8.516,8.064c-0.247,4.531,3.081,8.457,7.586,8.951
69
+ c26.53,2.91,26.53,16.418,26.53,20.847c0,13.773-11.418,30.404-25.6,30.404H349.867c-14.763,0-42.667,8.917-42.667,42.667
70
+ c0,4.71,3.814,8.533,8.533,8.533s8.533-3.823,8.533-8.533c0-24.09,21.367-25.549,25.6-25.6h119.467
71
+ c24.414,0,42.667-25.054,42.667-47.471C512,242.603,509.397,229.683,495.736,221.227z"
72
+ />
73
+ <path
74
+ d="M349.867,315.733c-4.719,0-8.533,3.823-8.533,8.533v102.4c0,29.508-13.158,42.667-42.667,42.667
75
+ c-5.342,0-8.533-3.192-8.533-8.533c0-68.932,0-114.466-36.634-151.1c-16.87-16.87-48.947-45.167-74.3-45.167
76
+ c-4.719,0-8.533,3.823-8.533,8.533s3.814,8.533,8.533,8.533c13.611,0,37.461,15.394,62.234,40.166
77
+ c31.633,31.633,31.633,72.055,31.633,139.034c0,14.831,10.769,25.6,25.6,25.6c39.074,0,59.733-20.651,59.733-59.733v-102.4
78
+ C358.4,319.556,354.586,315.733,349.867,315.733z"
79
+ />
80
+ <path
81
+ d="M145.067,25.6H8.533C3.814,25.6,0,29.423,0,34.133v256c0,4.71,3.814,8.533,8.533,8.533h136.533
82
+ c4.719,0,8.533-3.823,8.533-8.533v-256C153.6,29.423,149.786,25.6,145.067,25.6z M136.533,281.6H17.067V42.667h119.467V281.6z"
83
+ />
84
+ </g>
85
+ </g>
86
+ </g>
87
+ </svg>
88
+ </Icon>
89
+ <span>{text || 'No'}</span>
90
+ </Wrapper>
91
+ );
92
+
93
+ const Wrapper = styled.div`
94
+ font-family: var(--font-family-base);
95
+ display: flex;
96
+ color: var(--color-primary-500);
97
+ &:hover {
98
+ color: var(--color-primary-600);
99
+ svg {
100
+ > g {
101
+ fill: var(--color-primary-600);
102
+ }
103
+ }
104
+ }
105
+ `;
106
+
107
+ const Icon = styled.div`
108
+ width: 18px;
109
+ height: 18px;
110
+ margin-right: 5px;
111
+ > svg {
112
+ > g {
113
+ fill: var(--color-primary-500);
114
+ }
115
+ }
116
+ `;
@@ -0,0 +1,5 @@
1
+ export { Rating } from '@theme/Feedback/Rating';
2
+ export { Sentiment } from '@theme/Feedback/Sentiment';
3
+ export { Comment } from '@theme/Feedback/Comment';
4
+ export { Reasons } from '@theme/Feedback/Reasons';
5
+ export * from './types';
@@ -0,0 +1,63 @@
1
+ export type RatingProps = {
2
+ onSubmit: (value: { score: number; comment?: string; reasons?: string[] }) => void;
3
+ settings?: {
4
+ label?: string;
5
+ max?: number;
6
+ submitText?: string;
7
+ comment?: {
8
+ enable: boolean;
9
+ label?: string;
10
+ };
11
+ reasons?: {
12
+ enable: boolean;
13
+ label?: string;
14
+ multi?: boolean;
15
+ items: string[];
16
+ };
17
+ };
18
+ };
19
+
20
+ export type SentimentProps = {
21
+ onSubmit: (value: { score: number; comment?: string; reasons?: string[] }) => void;
22
+ settings?: {
23
+ label?: string;
24
+ submitText?: string;
25
+ comment?: {
26
+ enable: boolean;
27
+ likeLabel?: string;
28
+ dislikeLabel?: string;
29
+ };
30
+ reasons?: {
31
+ enable: boolean;
32
+ label?: string;
33
+ multi?: boolean;
34
+ items: string[];
35
+ };
36
+ };
37
+ };
38
+
39
+ export type CommentProps = {
40
+ onSubmit: (value: { comment: string }) => void;
41
+ settings?: {
42
+ label?: string;
43
+ submitText?: string;
44
+ };
45
+ };
46
+
47
+ export type ReasonsProps = {
48
+ onSubmit: (value: { reasons: string[] }) => void;
49
+ settings: {
50
+ label?: string;
51
+ multi?: boolean;
52
+ items: string[];
53
+ submitText?: string;
54
+ buttonText?: string;
55
+ };
56
+ };
57
+
58
+ export type ProblemProps = {
59
+ onSubmit: (value: string, location: string) => void; // TODO: maybe we don't need location here
60
+ settings?: {
61
+ label?: string;
62
+ };
63
+ };
@@ -11,6 +11,7 @@ import { useThemeConfig } from '@theme/hooks';
11
11
  type MarkdownLayoutProps = {
12
12
  tableOfContent: React.ReactNode;
13
13
  markdownWrapper: React.ReactNode;
14
+ feedback: React.ReactNode;
14
15
  editPage?: {
15
16
  to: string;
16
17
  text: string;
@@ -23,13 +24,14 @@ type MarkdownLayoutProps = {
23
24
  export function MarkdownLayout({
24
25
  tableOfContent,
25
26
  markdownWrapper,
27
+ feedback,
26
28
  editPage,
27
29
  lastModified,
28
30
  }: MarkdownLayoutProps): JSX.Element {
29
31
  const { markdown } = useThemeConfig();
30
32
  const { editPage: themeEditPage } = markdown || {};
31
33
 
32
- const mergedConf = editPage ? { ...editPage, ...themeEditPage } : undefined;
34
+ const mergedConf = editPage ? { ...themeEditPage, ...editPage } : undefined;
33
35
 
34
36
  return (
35
37
  <PageWrapper data-component-name="Markdown/MarkdownLayout">
@@ -41,6 +43,7 @@ export function MarkdownLayout({
41
43
  )}
42
44
  </LayoutTop>
43
45
  {markdownWrapper}
46
+ <LayoutBottom>{feedback}</LayoutBottom>
44
47
  <PageNavigation />
45
48
  </ContainerWrapper>
46
49
  {tableOfContent}
@@ -53,3 +56,9 @@ const LayoutTop = styled.div`
53
56
  justify-content: space-between;
54
57
  flex-flow: row nowrap;
55
58
  `;
59
+
60
+ const LayoutBottom = styled(LayoutTop)`
61
+ > * {
62
+ margin: 25px 5px;
63
+ }
64
+ `;
@@ -23,15 +23,21 @@ export function Tab({ activeTab, label, onClick }: TabProps): JSX.Element {
23
23
  const TabItem = styled.li<{ active: boolean }>`
24
24
  display: inline-block;
25
25
  list-style: none;
26
- margin-bottom: -1px;
27
26
  padding: 0.75rem 1rem;
28
27
  cursor: pointer;
29
28
 
30
29
  ${({ active }) =>
31
- active &&
30
+ active
31
+ ? `
32
+ background-color: var(--background-color);
33
+ border: solid var(--md-tabs-active-tab-border-color);
34
+ border-width: 0 0 1px 0;
35
+ color: var(--text-color);
32
36
  `
33
- background-color: white;
34
- border: solid #ccc;
35
- border-width: 1px 1px 0 1px;
37
+ : `
38
+ &:hover {
39
+ border: solid var(--md-tabs-hover-tab-border-color);
40
+ border-width: 0 0 1px 0;
41
+ }
36
42
  `}
37
43
  `;
@@ -31,17 +31,26 @@ export function Tabs({ children }: TabsProps): JSX.Element {
31
31
 
32
32
  const TabsContainer = styled.div`
33
33
  margin: 10px 0;
34
+
35
+ ol[class^='Tabs__TabList'] {
36
+ margin: 0;
37
+ padding: 0;
38
+ }
34
39
  `;
35
40
 
36
41
  const TabList = styled.ol`
37
- border-bottom: 1px solid #ccc;
42
+ color: var(--md-tabs-tab-text-color);
38
43
  padding: 0;
39
44
  margin-block-end: 0;
45
+ border: solid var(--color-secondary-400);
46
+ border-width: 0 0 1px 0;
47
+
48
+ li[class^='Tab__TabItem'] {
49
+ margin: 0;
50
+ margin-bottom: -1px;
51
+ }
40
52
  `;
41
53
 
42
54
  const TabContent = styled.div`
43
- padding: 10px 5px;
44
- border: 1px solid #ccc;
45
- border-top: none;
46
- padding: 1rem;
55
+ padding: 1rem 0;
47
56
  `;
@@ -3,6 +3,7 @@ import styled from 'styled-components';
3
3
 
4
4
  import { NavbarMenu } from '@theme/Navbar';
5
5
  import { useMobileMenu } from '@theme/hooks/useMobileMenu';
6
+ import { isEmptyArray, isPrimitive } from '@theme/utils';
6
7
  import { MobileNavbarMenuButton } from '@theme/Navbar/MobileNavbarMenuButton';
7
8
  import { MobileNavbarMenu } from '@theme/Navbar/MobileNavbarMenu';
8
9
  import { ColorModeSwitcher } from '@theme/ColorModeSwitcher/ColorModeSwitcher';
@@ -18,9 +19,10 @@ interface NavbarProps {
18
19
 
19
20
  export function Navbar({ menu, logo, search, profile }: NavbarProps): JSX.Element | null {
20
21
  const [isOpen, setIsOpen] = useMobileMenu(false);
21
- const { search: searchSettings, navbar } = useThemeConfig();
22
+ const { search: searchSettings, navbar, userProfile: userProfileSettings } = useThemeConfig();
22
23
  const hideSearch =
23
24
  searchSettings?.hide || (searchSettings?.placement && searchSettings?.placement !== 'navbar');
25
+ const hideUserProfile = userProfileSettings?.hide;
24
26
 
25
27
  if (navbar?.hide) {
26
28
  return null;
@@ -29,9 +31,12 @@ export function Navbar({ menu, logo, search, profile }: NavbarProps): JSX.Elemen
29
31
  const openMobileMenu = () => setIsOpen(true);
30
32
  const closeMobileMenu = () => setIsOpen(false);
31
33
 
34
+ const menuItemsExist = !isPrimitive(menu) && !isEmptyArray(menu);
35
+
32
36
  return (
33
37
  <NavbarContainer data-component-name="Navbar/Navbar">
34
- <MobileNavbarMenuButton onClick={openMobileMenu} />
38
+ {menuItemsExist && <MobileNavbarMenuButton onClick={openMobileMenu} />}
39
+
35
40
  {isOpen && (
36
41
  <MobileNavbarMenu
37
42
  closeMenu={closeMobileMenu}
@@ -43,7 +48,7 @@ export function Navbar({ menu, logo, search, profile }: NavbarProps): JSX.Elemen
43
48
  {logo}
44
49
  <NavbarMenu menuItems={menu} />
45
50
  {hideSearch ? null : search}
46
- {profile}
51
+ {hideUserProfile ? null : profile}
47
52
  <ColorModeSwitcher />
48
53
  </NavbarRow>
49
54
  </NavbarContainer>
@@ -0,0 +1,42 @@
1
+ import React from 'react';
2
+ import styled from 'styled-components';
3
+
4
+ import { Button } from '@theme/Button';
5
+
6
+ export function Forbidden(): JSX.Element {
7
+ return (
8
+ <Wrapper data-component-name="Pages/Forbidden">
9
+ <Header>403</Header>
10
+ <Description>Access forbidden</Description>
11
+ <HomeButton color="primary" size="large" to="/">
12
+ Open Homepage
13
+ </HomeButton>
14
+ </Wrapper>
15
+ );
16
+ }
17
+
18
+ const Wrapper = styled.div`
19
+ margin: 25px auto;
20
+ font-family: var(--page-403-font-family);
21
+ text-align: center;
22
+ `;
23
+
24
+ const Header = styled.div`
25
+ color: var(--page-403-header-text-color);
26
+ margin: var(--page-403-header-margin);
27
+ font-size: var(--page-403-header-font-size);
28
+ line-height: var(--page-403-header-line-height);
29
+ font-weight: var(--page-403-header-font-weight);
30
+ `;
31
+
32
+ const Description = styled.div`
33
+ color: var(--page-403-description-text-color);
34
+ margin: var(--page-403-description-margin);
35
+ font-size: var(--page-403-description-font-size);
36
+ line-height: var(--page-403-description-line-height);
37
+ font-weight: var(--page-403-description-font-weight);
38
+ `;
39
+
40
+ const HomeButton = styled(Button)`
41
+ margin-top: var(--page-403-button-margin);
42
+ `;
@@ -0,0 +1,42 @@
1
+ import React from 'react';
2
+ import styled from 'styled-components';
3
+
4
+ import { Button } from '@theme/Button';
5
+
6
+ export function NotFound(): JSX.Element {
7
+ return (
8
+ <Wrapper data-component-name="Pages/NotFound">
9
+ <Header>404</Header>
10
+ <Description>It looks like you&apos;re lost</Description>
11
+ <HomeButton color="primary" size="large" to="/">
12
+ Open Homepage
13
+ </HomeButton>
14
+ </Wrapper>
15
+ );
16
+ }
17
+
18
+ const Wrapper = styled.div`
19
+ margin: 25px auto;
20
+ font-family: var(--page-404-font-family);
21
+ text-align: center;
22
+ `;
23
+
24
+ const Header = styled.div`
25
+ color: var(--page-404-header-text-color);
26
+ margin: var(--page-404-header-margin);
27
+ font-size: var(--page-404-header-font-size);
28
+ line-height: var(--page-404-header-line-height);
29
+ font-weight: var(--page-404-header-font-weight);
30
+ `;
31
+
32
+ const Description = styled.div`
33
+ color: var(--page-404-description-text-color);
34
+ margin: var(--page-404-description-margin);
35
+ font-size: var(--page-404-description-font-size);
36
+ line-height: var(--page-404-description-line-height);
37
+ font-weight: var(--page-404-description-font-weight);
38
+ `;
39
+
40
+ const HomeButton = styled(Button)`
41
+ margin-top: var(--page-404-button-margin);
42
+ `;
@@ -0,0 +1 @@
1
+ export * from '@theme/Pages/NotFound';
@@ -0,0 +1,29 @@
1
+ import React from 'react';
2
+ import styled from 'styled-components';
3
+
4
+ import { useThemeConfig } from '@theme/hooks/useThemeConfig';
5
+
6
+ export interface LoginLinkProps {
7
+ href: string;
8
+ }
9
+
10
+ export function LoginLink({ href }: LoginLinkProps): JSX.Element {
11
+ const { userProfile } = useThemeConfig();
12
+ return <StyledLink href={href}>{userProfile?.loginLabel || 'Login'}</StyledLink>;
13
+ }
14
+
15
+ const StyledLink = styled.a.attrs(() => ({
16
+ 'data-component-name': 'Profile/LoginLink',
17
+ }))`
18
+ display: inline-block;
19
+ color: var(--navbar-text-color);
20
+ text-decoration: none;
21
+ padding: 0 var(--navbar-item-padding-horizontal);
22
+ text-align: center;
23
+ line-height: 1;
24
+ font-size: var(--navbar-item-font-size);
25
+ font-weight: var(--navbar-item-font-weight);
26
+ &:hover {
27
+ color: var(--navbar-item-hover-text-color);
28
+ }
29
+ `;
@@ -44,7 +44,9 @@ function ProfileComponent({ name, imageUrl, onClick, color }: ProfileProps): JSX
44
44
 
45
45
  export const Profile = memo<ProfileProps>(ProfileComponent);
46
46
 
47
- const ProfileWrapper = styled.div`
47
+ const ProfileWrapper = styled.div.attrs(() => ({
48
+ 'data-component-name': 'Profile/Profile',
49
+ }))`
48
50
  display: flex;
49
51
  align-items: center;
50
52
  cursor: pointer;
@@ -0,0 +1,101 @@
1
+ import React, { useState } from 'react';
2
+ import styled from 'styled-components';
3
+
4
+ import { Link } from '@portal/Link';
5
+ import { Profile } from '@theme/Profile/Profile';
6
+ import { Tooltip } from '@theme/Tooltip/Tooltip';
7
+ import { useThemeConfig } from '@theme/hooks/useThemeConfig';
8
+
9
+ export interface UserProfileProps {
10
+ userInfo: {
11
+ isAuthenticated: boolean;
12
+ name: string;
13
+ picture: string;
14
+ logoutDisabled?: boolean;
15
+ };
16
+ handleLogout: (logoutRedirect?: string) => void;
17
+ hasDeveloperOnboarding?: boolean;
18
+ hasApiLogs?: boolean;
19
+ }
20
+
21
+ export function UserProfile({
22
+ userInfo,
23
+ handleLogout,
24
+ hasDeveloperOnboarding = false,
25
+ hasApiLogs = false,
26
+ }: UserProfileProps): JSX.Element {
27
+ const [isOpened, setIsOpened] = useState<boolean>(false);
28
+
29
+ const { userProfile: userProfileSettings } = useThemeConfig();
30
+
31
+ const logoutRedirect = userProfileSettings?.logoutRedirect || '/';
32
+
33
+ return (
34
+ <StyledTooltip
35
+ isOpen={isOpened}
36
+ withArrow={false}
37
+ className="copy-button"
38
+ placement="bottom"
39
+ width="100%"
40
+ tip={
41
+ <StyledUl>
42
+ {hasDeveloperOnboarding ? (
43
+ <Link to="/apps">
44
+ <StyledLi>My Apps</StyledLi>
45
+ </Link>
46
+ ) : null}
47
+ {hasApiLogs ? (
48
+ <Link to="/api-logs">
49
+ <StyledLi>API logs</StyledLi>
50
+ </Link>
51
+ ) : null}
52
+ <StyledLi onClick={() => handleLogout(logoutRedirect)}>
53
+ {userProfileSettings?.logoutLabel || 'Log out'}
54
+ </StyledLi>
55
+ </StyledUl>
56
+ }
57
+ >
58
+ <Profile
59
+ name={userInfo.name}
60
+ imageUrl={userInfo.picture}
61
+ onClick={userInfo.logoutDisabled ? undefined : () => setIsOpened(!isOpened)}
62
+ />
63
+ </StyledTooltip>
64
+ );
65
+ }
66
+
67
+ const StyledTooltip = styled(Tooltip)`
68
+ > span {
69
+ padding: 0;
70
+ }
71
+ `;
72
+
73
+ const StyledUl = styled.ul`
74
+ margin: 0;
75
+ padding: 0;
76
+ list-style: none;
77
+ text-align: left;
78
+ background-color: var(--search-modal-background);
79
+ color: var(--search-modal-text-color);
80
+ min-width: 100px;
81
+ a {
82
+ text-decoration: none;
83
+ &:hover {
84
+ color: inherit;
85
+ }
86
+ &:visited {
87
+ color: inherit;
88
+ }
89
+ }
90
+ `;
91
+
92
+ const StyledLi = styled.li`
93
+ cursor: pointer;
94
+ font-size: 16px;
95
+ list-style: none;
96
+ padding: 15px 20px;
97
+ transition: background-color 0.25s ease 0s;
98
+ &:hover {
99
+ background-color: rgba(0, 0, 0, 0.1);
100
+ }
101
+ `;
@@ -1,2 +1,6 @@
1
1
  export { Profile } from './Profile';
2
2
  export type { ProfileProps } from './Profile';
3
+ export { LoginLink } from './LoginLink';
4
+ export type { LoginLinkProps } from './LoginLink';
5
+ export { UserProfile } from './UserProfile';
6
+ export type { UserProfileProps } from './UserProfile';
@@ -1,11 +1,13 @@
1
- import React, { useEffect, useState } from 'react';
1
+ import React, { useEffect, useRef, useState } from 'react';
2
2
  import { useLocation } from 'react-router-dom';
3
3
  import styled from 'styled-components';
4
+ import hotkeys from 'hotkeys-js';
4
5
 
5
6
  import type { ChangeEvent, KeyboardEvent, ReactNode, SyntheticEvent } from 'react';
6
7
 
7
8
  import { FormInput } from '@theme/Search/Input';
8
9
  import { Popover } from '@theme/Search/Popover';
10
+ import { ShortcutKey } from '@theme/Search/ShortcutKey';
9
11
  import type { ActiveItem } from '@portal/types';
10
12
 
11
13
  interface AutocompleteProps<T> {
@@ -16,6 +18,8 @@ interface AutocompleteProps<T> {
16
18
  change(value: string): void;
17
19
  select(item: T): void;
18
20
  children?(isOpen: boolean, close: () => void): ReactNode;
21
+ inputRef?: React.RefObject<HTMLInputElement>;
22
+ keyShortcuts?: string[];
19
23
  }
20
24
 
21
25
  export function Autocomplete<T>({
@@ -26,12 +30,28 @@ export function Autocomplete<T>({
26
30
  select,
27
31
  renderItem,
28
32
  children,
33
+ keyShortcuts,
29
34
  }: AutocompleteProps<T>): JSX.Element {
30
35
  const location = useLocation();
31
36
  const [isOpen, setIsOpen] = useState(false);
32
37
  const [activeIdx, setActiveIdx] = useState(-1);
38
+ const refInput = useRef<HTMLInputElement>(null);
39
+
40
+ const hotkeysKeys = keyShortcuts?.join(',');
41
+
42
+ useEffect(() => {
43
+ if (hotkeysKeys) {
44
+ hotkeys(hotkeysKeys, (ev) => {
45
+ refInput.current?.focus();
46
+ ev.preventDefault();
47
+ });
48
+
49
+ return () => hotkeys.unbind(hotkeysKeys as string);
50
+ }
51
+ }, [hotkeysKeys]);
33
52
 
34
53
  const open = () => setIsOpen(true);
54
+
35
55
  const close = () => {
36
56
  setIsOpen(false);
37
57
  setActiveIdx(-1);
@@ -65,6 +85,7 @@ export function Autocomplete<T>({
65
85
  break;
66
86
  case 'Enter':
67
87
  if (activeIdx > -1) {
88
+ reset();
68
89
  select(items[activeIdx]);
69
90
  }
70
91
  break;
@@ -82,15 +103,18 @@ export function Autocomplete<T>({
82
103
  <Wrapper data-component-name="Search/Autocomplete">
83
104
  {isOpen ? <Overlay onClick={close} /> : null}
84
105
  <AutocompleteBox onKeyDown={onKeydown}>
106
+ {children?.(isOpen, reset)}
107
+
85
108
  <FormInput
86
109
  value={value}
87
110
  placeholder={placeholder}
88
111
  onChange={onChange}
89
112
  onFocus={open}
90
113
  onClick={stopPropagation}
114
+ ref={refInput}
91
115
  />
92
116
 
93
- {children?.(isOpen, reset)}
117
+ {!isOpen && <ShortcutKey keyShortcuts={keyShortcuts} />}
94
118
 
95
119
  {isOpen && value ? (
96
120
  <Popover>{items.length ? items.map(mapItem) : <Message>No results</Message>}</Popover>
@@ -24,7 +24,7 @@ export const ClearIcon = styled(Icon).attrs(() => ({
24
24
  cursor: pointer;
25
25
  width: 0.5em;
26
26
  height: 0.5em;
27
- right: 1.2em;
27
+ left: 1.2em;
28
28
  fill: var(--search-input-text-color);
29
29
 
30
30
  ${({ theme }) => theme.mediaQueries.medium} {
@@ -3,7 +3,7 @@ import styled from 'styled-components';
3
3
  export const FormInput = styled.input.attrs(() => ({
4
4
  'data-component-name': 'Search/Input',
5
5
  }))`
6
- padding: 1em 2.5em 1em 1em;
6
+ padding: 1em 2.5em 1em 2.5em;
7
7
  background-color: var(--search-input-background-color);
8
8
  border-radius: var(--search-input-border-radius);
9
9
  border: var(--search-input-border);