@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.
- package/lib/Feedback/Comment.d.ts +3 -0
- package/lib/Feedback/Comment.js +80 -0
- package/lib/Feedback/Rating.d.ts +3 -0
- package/lib/Feedback/Rating.js +82 -0
- package/lib/Feedback/Reasons.d.ts +3 -0
- package/lib/Feedback/Reasons.js +85 -0
- package/lib/Feedback/Sentiment.d.ts +3 -0
- package/lib/Feedback/Sentiment.js +79 -0
- package/lib/Feedback/Thumbs.d.ts +7 -0
- package/lib/Feedback/Thumbs.js +79 -0
- package/lib/Feedback/index.d.ts +5 -0
- package/lib/Feedback/index.js +27 -0
- package/lib/Feedback/types.d.ts +71 -0
- package/lib/Feedback/types.js +3 -0
- package/lib/Markdown/MarkdownLayout.d.ts +2 -1
- package/lib/Markdown/MarkdownLayout.js +8 -2
- package/lib/Markdown/Tabs/Tab.js +11 -5
- package/lib/Markdown/Tabs/Tabs.js +14 -5
- package/lib/Navbar/Navbar.js +6 -3
- package/lib/Pages/Forbidden.d.ts +2 -0
- package/lib/Pages/Forbidden.js +39 -0
- package/lib/Pages/NotFound.d.ts +2 -0
- package/lib/Pages/NotFound.js +39 -0
- package/lib/Pages/index.d.ts +1 -0
- package/lib/Pages/index.js +18 -0
- package/lib/Profile/LoginLink.d.ts +5 -0
- package/lib/Profile/LoginLink.js +30 -0
- package/lib/Profile/Profile.js +3 -1
- package/lib/Profile/UserProfile.d.ts +13 -0
- package/lib/Profile/UserProfile.js +82 -0
- package/lib/Profile/index.d.ts +4 -0
- package/lib/Profile/index.js +5 -1
- package/lib/Search/Autocomplete.d.ts +4 -1
- package/lib/Search/Autocomplete.js +19 -3
- package/lib/Search/ClearIcon.js +1 -1
- package/lib/Search/Input.js +1 -1
- package/lib/Search/Search.js +6 -1
- package/lib/Search/SearchIcon.js +1 -1
- package/lib/Search/ShortcutKey.d.ts +7 -0
- package/lib/Search/ShortcutKey.js +35 -0
- package/lib/config.d.ts +61 -0
- package/lib/config.js +19 -0
- package/lib/globalStyle.js +62 -0
- package/lib/index.d.ts +2 -0
- package/lib/index.js +2 -0
- package/lib/mocks/Link.js +1 -1
- package/lib/mocks/hooks/index.js +11 -1
- package/lib/mocks/search.js +18 -5
- package/lib/ui/Box.d.ts +2 -2
- package/lib/ui/Box.js +1 -0
- package/lib/ui/darkColors.js +5 -0
- package/package.json +9 -5
- package/src/Feedback/Comment.tsx +64 -0
- package/src/Feedback/Rating.tsx +107 -0
- package/src/Feedback/Reasons.tsx +81 -0
- package/src/Feedback/Sentiment.tsx +75 -0
- package/src/Feedback/Thumbs.tsx +116 -0
- package/src/Feedback/index.ts +5 -0
- package/src/Feedback/types.ts +63 -0
- package/src/Markdown/MarkdownLayout.tsx +10 -1
- package/src/Markdown/Tabs/Tab.tsx +11 -5
- package/src/Markdown/Tabs/Tabs.tsx +14 -5
- package/src/Navbar/Navbar.tsx +8 -3
- package/src/Pages/Forbidden.tsx +42 -0
- package/src/Pages/NotFound.tsx +42 -0
- package/src/Pages/index.ts +1 -0
- package/src/Profile/LoginLink.tsx +29 -0
- package/src/Profile/Profile.tsx +3 -1
- package/src/Profile/UserProfile.tsx +101 -0
- package/src/Profile/index.ts +4 -0
- package/src/Search/Autocomplete.tsx +26 -2
- package/src/Search/ClearIcon.tsx +1 -1
- package/src/Search/Input.tsx +1 -1
- package/src/Search/Search.tsx +3 -0
- package/src/Search/SearchIcon.tsx +1 -1
- package/src/Search/ShortcutKey.tsx +35 -0
- package/src/config.ts +23 -0
- package/src/globalStyle.ts +64 -0
- package/src/index.ts +2 -0
- package/src/mocks/Link.tsx +2 -1
- package/src/mocks/hooks/index.ts +11 -1
- package/src/mocks/search.ts +20 -5
- package/src/settings.yaml +2 -0
- package/src/types/portal/index.d.ts +1 -1
- package/src/ui/Box.tsx +5 -2
- 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,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 ? { ...
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
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:
|
|
44
|
-
border: 1px solid #ccc;
|
|
45
|
-
border-top: none;
|
|
46
|
-
padding: 1rem;
|
|
55
|
+
padding: 1rem 0;
|
|
47
56
|
`;
|
package/src/Navbar/Navbar.tsx
CHANGED
|
@@ -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'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
|
+
`;
|
package/src/Profile/Profile.tsx
CHANGED
|
@@ -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
|
+
`;
|
package/src/Profile/index.ts
CHANGED
|
@@ -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
|
-
{
|
|
117
|
+
{!isOpen && <ShortcutKey keyShortcuts={keyShortcuts} />}
|
|
94
118
|
|
|
95
119
|
{isOpen && value ? (
|
|
96
120
|
<Popover>{items.length ? items.map(mapItem) : <Message>No results</Message>}</Popover>
|
package/src/Search/ClearIcon.tsx
CHANGED
package/src/Search/Input.tsx
CHANGED
|
@@ -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
|
|
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);
|