@redocly/theme 0.2.0 → 0.2.3
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/ColorModeSwitcher/ColorModeSwitcher.d.ts +2 -0
- package/ColorModeSwitcher/ColorModeSwitcher.js +79 -0
- package/ColorModeSwitcher/index.d.ts +1 -0
- package/ColorModeSwitcher/index.js +17 -0
- package/Markdown/MarkdownWrapper.js +1 -1
- package/Navbar/Navbar.js +3 -1
- package/SourceCode/SourceCode.js +54 -9
- package/TableOfContent/TableOfContent.js +7 -18
- package/TableOfContent/utils.d.ts +4 -0
- package/TableOfContent/utils.js +65 -0
- package/globalStyle.d.ts +2 -0
- package/globalStyle.js +27 -24
- package/icons/ColorModeIcon/ColorModeIcon.d.ts +10 -0
- package/icons/ColorModeIcon/ColorModeIcon.js +30 -0
- package/icons/ColorModeIcon/index.d.ts +2 -0
- package/icons/ColorModeIcon/index.js +5 -0
- package/icons/index.d.ts +1 -0
- package/icons/index.js +1 -0
- package/index.d.ts +1 -0
- package/index.js +1 -0
- package/mocks/hooks/index.js +4 -0
- package/package.json +1 -1
- package/src/ColorModeSwitcher/ColorModeSwitcher.tsx +47 -0
- package/src/ColorModeSwitcher/index.ts +1 -0
- package/src/Markdown/MarkdownWrapper.tsx +24 -0
- package/src/Navbar/Navbar.tsx +2 -0
- package/src/SourceCode/SourceCode.tsx +16 -5
- package/src/TableOfContent/TableOfContent.tsx +11 -18
- package/src/TableOfContent/utils.ts +45 -0
- package/src/globalStyle.ts +30 -1
- package/src/icons/ColorModeIcon/ColorModeIcon.tsx +53 -0
- package/src/icons/ColorModeIcon/index.ts +2 -0
- package/src/icons/index.ts +1 -0
- package/src/index.ts +1 -0
- package/src/mocks/hooks/index.ts +4 -0
- package/{settings.yaml → src/settings.yaml} +6 -0
|
@@ -246,6 +246,30 @@ export const MarkdownWrapper = styled.main.attrs(() => ({
|
|
|
246
246
|
${headingAnchor()};
|
|
247
247
|
}
|
|
248
248
|
|
|
249
|
+
h1.md code {
|
|
250
|
+
font-size: var(--h1-font-size);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
h2.md code {
|
|
254
|
+
font-size: var(--h2-font-size);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
h3.md code {
|
|
258
|
+
font-size: var(--h3-font-size);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
h4.md code {
|
|
262
|
+
font-size: var(--h4-font-size);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
h5.md code {
|
|
266
|
+
font-size: var(--h5-font-size);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
h6.md code {
|
|
270
|
+
font-size: var(--h6-font-size);
|
|
271
|
+
}
|
|
272
|
+
|
|
249
273
|
code {
|
|
250
274
|
color: var(--inline-code-color);
|
|
251
275
|
background-color: var(--inline-code-background-color);
|
package/src/Navbar/Navbar.tsx
CHANGED
|
@@ -8,6 +8,7 @@ import { NavbarMenu } from '@theme/Navbar';
|
|
|
8
8
|
import { useMobileMenu } from '@theme/hooks/useMobileMenu';
|
|
9
9
|
import { MobileNavbarMenuButton } from '@theme/Navbar/MobileNavbarMenuButton';
|
|
10
10
|
import { MobileNavbarMenu } from '@theme/Navbar/MobileNavbarMenu';
|
|
11
|
+
import { ColorModeSwitcher } from '@theme/ColorModeSwitcher/ColorModeSwitcher';
|
|
11
12
|
|
|
12
13
|
interface NavbarProps {
|
|
13
14
|
menu: ResolvedConfigLinks;
|
|
@@ -37,6 +38,7 @@ export function Navbar({ menu, logo, search, profile }: NavbarProps): JSX.Elemen
|
|
|
37
38
|
<NavbarMenu menuItems={menu} />
|
|
38
39
|
{hideSearch ? null : search}
|
|
39
40
|
{profile}
|
|
41
|
+
<ColorModeSwitcher />
|
|
40
42
|
</NavbarContainer>
|
|
41
43
|
);
|
|
42
44
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
2
|
|
|
3
3
|
import { highlight, addLineNumbers } from '@theme/utils';
|
|
4
4
|
import {
|
|
@@ -72,16 +72,27 @@ export function SourceCode({
|
|
|
72
72
|
withLineNumbers,
|
|
73
73
|
startLineNumber,
|
|
74
74
|
}: SourceCodeProps): JSX.Element {
|
|
75
|
-
const
|
|
75
|
+
const [sourceCode, setSourceCode] = useState<string>(source ?? '');
|
|
76
|
+
|
|
77
|
+
// The same initial value should be returned for ssr and frontend to avoid issues
|
|
78
|
+
// Because we don't have session storage in ssr and can't get the security details there
|
|
79
|
+
// Issue for more details https://github.com/Redocly/reference-docs/issues/888
|
|
80
|
+
useEffect(() => {
|
|
81
|
+
const _externalSource = externalSource?.sample?.get?.(externalSource) ?? '';
|
|
82
|
+
if (_externalSource) {
|
|
83
|
+
setSourceCode(_externalSource);
|
|
84
|
+
}
|
|
85
|
+
}, [externalSource]);
|
|
86
|
+
|
|
76
87
|
if (withCopyButton) {
|
|
77
88
|
return (
|
|
78
|
-
<CopyButtonWrapper data={
|
|
89
|
+
<CopyButtonWrapper data={sourceCode} data-component-name="SourceCode/SourceCode">
|
|
79
90
|
{({ renderCopyButton }) => (
|
|
80
91
|
<SampleControlsWrap>
|
|
81
92
|
<SampleControls data-cy="copy-button">{renderCopyButton()}</SampleControls>
|
|
82
93
|
<Code
|
|
83
94
|
lang={lang}
|
|
84
|
-
source={
|
|
95
|
+
source={sourceCode}
|
|
85
96
|
withLineNumbers={withLineNumbers}
|
|
86
97
|
startLineNumber={startLineNumber}
|
|
87
98
|
dataTestId={dataTestId}
|
|
@@ -96,7 +107,7 @@ export function SourceCode({
|
|
|
96
107
|
<Code
|
|
97
108
|
dataTestId={dataTestId}
|
|
98
109
|
lang={lang}
|
|
99
|
-
source={
|
|
110
|
+
source={sourceCode}
|
|
100
111
|
withLineNumbers={withLineNumbers}
|
|
101
112
|
startLineNumber={startLineNumber}
|
|
102
113
|
data-component-name="SourceCode/SourceCode"
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import React, { useRef } from 'react';
|
|
2
2
|
import styled from 'styled-components';
|
|
3
3
|
|
|
4
|
+
import { getDisplayedHeadingsIds, getDisplayedHeadings, getLeastDepth } from './utils';
|
|
5
|
+
|
|
4
6
|
import { useFullHeight } from '@theme/hooks/useFullHeight';
|
|
5
7
|
import { useThemeSettings } from '@portal/hooks';
|
|
6
8
|
import { useActiveHeading } from '@theme/hooks/useActiveHeading';
|
|
@@ -20,16 +22,13 @@ export function TableOfContent(props: TableOfContentProps): JSX.Element | null {
|
|
|
20
22
|
useFullHeight(sidebar);
|
|
21
23
|
const { toc } = useThemeSettings(DEFAULT_THEME_NAME);
|
|
22
24
|
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
return [];
|
|
26
|
-
}
|
|
27
|
-
return headings
|
|
28
|
-
.filter((header) => header && tocMaxDepth >= header.depth)
|
|
29
|
-
.map((header) => header?.id);
|
|
30
|
-
};
|
|
25
|
+
const displayedHeadings = getDisplayedHeadings(headings, tocMaxDepth);
|
|
26
|
+
const leastDepth = getLeastDepth(displayedHeadings);
|
|
31
27
|
|
|
32
|
-
const activeHeadingId = useActiveHeading(
|
|
28
|
+
const activeHeadingId = useActiveHeading(
|
|
29
|
+
contentWrapper,
|
|
30
|
+
getDisplayedHeadingsIds(displayedHeadings),
|
|
31
|
+
);
|
|
33
32
|
|
|
34
33
|
if (toc?.hide) {
|
|
35
34
|
return null;
|
|
@@ -47,23 +46,17 @@ export function TableOfContent(props: TableOfContentProps): JSX.Element | null {
|
|
|
47
46
|
<TableOfContentMenu data-component-name="TableOfContent/TableOfContent">
|
|
48
47
|
<TableOfContentItems ref={sidebar}>
|
|
49
48
|
<TocHeader>{toc?.header?.label || 'On this page'}</TocHeader>
|
|
50
|
-
{
|
|
49
|
+
{displayedHeadings.map((heading: MdHeading | null, idx: number) => {
|
|
51
50
|
// TODO: not sure about !heading
|
|
52
51
|
if (!heading) {
|
|
53
52
|
return null;
|
|
54
53
|
}
|
|
55
|
-
if (idx === 0 && heading.depth === 1) {
|
|
56
|
-
return null;
|
|
57
|
-
}
|
|
58
|
-
if (heading.depth && heading.depth > tocMaxDepth) {
|
|
59
|
-
return null;
|
|
60
|
-
}
|
|
61
54
|
const href = '#' + heading.id;
|
|
62
55
|
return (
|
|
63
56
|
<MenuItem
|
|
64
57
|
key={href + idx}
|
|
65
58
|
href={href}
|
|
66
|
-
depth={heading.depth || 0}
|
|
59
|
+
depth={heading.depth - leastDepth + 1 || 0}
|
|
67
60
|
className={activeHeadingId === heading.id ? 'active' : ''}
|
|
68
61
|
dangerouslySetInnerHTML={{ __html: heading.value || '' }}
|
|
69
62
|
data-cy={`toc-${heading.value}`}
|
|
@@ -90,7 +83,7 @@ const MenuItem = styled.a<{ depth: number }>`
|
|
|
90
83
|
cursor: pointer;
|
|
91
84
|
font-size: 0.8em;
|
|
92
85
|
padding: 10px 15px;
|
|
93
|
-
padding-left: ${({ depth }) =>
|
|
86
|
+
padding-left: ${({ depth }) => depth * 15}px;
|
|
94
87
|
transition: background-color 0.3s, color 0.3s;
|
|
95
88
|
text-decoration: none;
|
|
96
89
|
word-break: break-word;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { MdHeading } from '@theme/types/portal';
|
|
2
|
+
|
|
3
|
+
export function getDisplayedHeadings(
|
|
4
|
+
headings: Array<MdHeading | null> | null | undefined,
|
|
5
|
+
tocMaxDepth: number,
|
|
6
|
+
): Array<MdHeading | null> {
|
|
7
|
+
if (!headings) {
|
|
8
|
+
return [];
|
|
9
|
+
}
|
|
10
|
+
return headings.filter((heading, idx) => {
|
|
11
|
+
if (!heading) {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
if (idx === 0 && heading.depth === 1) {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
if (heading.depth && heading.depth > tocMaxDepth) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
return true;
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function getDisplayedHeadingsIds(
|
|
25
|
+
headings: Array<MdHeading | null> | null | undefined,
|
|
26
|
+
): Array<string | undefined> {
|
|
27
|
+
if (!headings) {
|
|
28
|
+
return [];
|
|
29
|
+
}
|
|
30
|
+
return headings.map((header) => header?.id);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function getLeastDepth(headings: Array<MdHeading | null> | null | undefined): number {
|
|
34
|
+
if (!headings || headings.length === 0) {
|
|
35
|
+
return 1;
|
|
36
|
+
}
|
|
37
|
+
let depth = null;
|
|
38
|
+
for (const heading of headings) {
|
|
39
|
+
if (!heading) continue;
|
|
40
|
+
if (depth === null || depth > heading.depth) {
|
|
41
|
+
depth = heading.depth;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return depth ?? 1;
|
|
45
|
+
}
|
package/src/globalStyle.ts
CHANGED
|
@@ -79,6 +79,23 @@ const baseColors = css`
|
|
|
79
79
|
|
|
80
80
|
// @tokens End
|
|
81
81
|
`;
|
|
82
|
+
const baseDarkColors = css`
|
|
83
|
+
/* === Palette === */
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* @tokens Base Colors
|
|
87
|
+
* @presenter Color
|
|
88
|
+
*/
|
|
89
|
+
--color-primary-100: #969ca6;
|
|
90
|
+
--color-primary-200: #7f8693;
|
|
91
|
+
--color-primary-300: #7d7d80;
|
|
92
|
+
--color-primary-400: #4b4f56;
|
|
93
|
+
--color-primary-500: #404042;
|
|
94
|
+
--color-primary-600: #36383d;
|
|
95
|
+
--color-primary-700: #28282a;
|
|
96
|
+
--color-primary-800: #202021;
|
|
97
|
+
--color-primary-900: #000000;
|
|
98
|
+
`;
|
|
82
99
|
|
|
83
100
|
const httpColors = css`
|
|
84
101
|
/**
|
|
@@ -812,6 +829,14 @@ const portalSearch = css`
|
|
|
812
829
|
// @tokens End
|
|
813
830
|
`;
|
|
814
831
|
|
|
832
|
+
export const lightMode = css`
|
|
833
|
+
${baseColors};
|
|
834
|
+
`;
|
|
835
|
+
|
|
836
|
+
export const darkMode = css`
|
|
837
|
+
${baseDarkColors}
|
|
838
|
+
`;
|
|
839
|
+
|
|
815
840
|
export const styles = css`
|
|
816
841
|
:root {
|
|
817
842
|
${baseColors}
|
|
@@ -837,8 +862,12 @@ export const styles = css`
|
|
|
837
862
|
|
|
838
863
|
${openapiAndGraphqlDocs}
|
|
839
864
|
}
|
|
865
|
+
|
|
866
|
+
:root.dark {
|
|
867
|
+
${darkMode};
|
|
868
|
+
}
|
|
840
869
|
`;
|
|
841
870
|
|
|
842
871
|
export const GlobalStyle = createGlobalStyle`
|
|
843
|
-
${styles}
|
|
872
|
+
${styles};
|
|
844
873
|
`;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import styled from 'styled-components';
|
|
3
|
+
|
|
4
|
+
export interface ColorModeIconProps {
|
|
5
|
+
mode?: 'dark' | 'light' | string;
|
|
6
|
+
className?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function Icon({ mode, className }: ColorModeIconProps) {
|
|
10
|
+
switch (mode) {
|
|
11
|
+
case 'dark':
|
|
12
|
+
return (
|
|
13
|
+
<svg
|
|
14
|
+
className={className}
|
|
15
|
+
data-testid="dark"
|
|
16
|
+
viewBox="0 0 16 16"
|
|
17
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
18
|
+
>
|
|
19
|
+
<path d="M6 .278a.768.768 0 0 1 .08.858 7.208 7.208 0 0 0-.878 3.46c0 4.021 3.278 7.277 7.318 7.277.527 0 1.04-.055 1.533-.16a.787.787 0 0 1 .81.316.733.733 0 0 1-.031.893A8.349 8.349 0 0 1 8.344 16C3.734 16 0 12.286 0 7.71 0 4.266 2.114 1.312 5.124.06A.752.752 0 0 1 6 .278z" />
|
|
20
|
+
</svg>
|
|
21
|
+
);
|
|
22
|
+
case 'light':
|
|
23
|
+
return (
|
|
24
|
+
<svg
|
|
25
|
+
data-testid="light"
|
|
26
|
+
className={className}
|
|
27
|
+
viewBox="0 0 16 16"
|
|
28
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
29
|
+
>
|
|
30
|
+
<path d="M12 8a4 4 0 1 1-8 0 4 4 0 0 1 8 0zM8 0a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 0zm0 13a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 13zm8-5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2a.5.5 0 0 1 .5.5zM3 8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2A.5.5 0 0 1 3 8zm10.657-5.657a.5.5 0 0 1 0 .707l-1.414 1.415a.5.5 0 1 1-.707-.708l1.414-1.414a.5.5 0 0 1 .707 0zm-9.193 9.193a.5.5 0 0 1 0 .707L3.05 13.657a.5.5 0 0 1-.707-.707l1.414-1.414a.5.5 0 0 1 .707 0zm9.193 2.121a.5.5 0 0 1-.707 0l-1.414-1.414a.5.5 0 0 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .707zM4.464 4.465a.5.5 0 0 1-.707 0L2.343 3.05a.5.5 0 1 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .708z" />
|
|
31
|
+
</svg>
|
|
32
|
+
);
|
|
33
|
+
default:
|
|
34
|
+
return (
|
|
35
|
+
<svg
|
|
36
|
+
data-testid="custom"
|
|
37
|
+
className={className}
|
|
38
|
+
viewBox="0 0 16 16"
|
|
39
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
40
|
+
>
|
|
41
|
+
<path d="M8 1a7 7 0 1 0 0 14A7 7 0 0 0 8 1zm0 13V2a6 6 0 1 1 0 12z" />
|
|
42
|
+
</svg>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export const ColorModeIcon = styled(Icon).attrs(() => ({
|
|
48
|
+
'data-component-name': 'icons/ColorModeIcon/ColorModeIcon',
|
|
49
|
+
}))`
|
|
50
|
+
width: var(--navbar-item-font-size);
|
|
51
|
+
box-sizing: border-box;
|
|
52
|
+
fill: var(--navbar-text-color);
|
|
53
|
+
`;
|
package/src/icons/index.ts
CHANGED
package/src/index.ts
CHANGED
package/src/mocks/hooks/index.ts
CHANGED
|
@@ -16,6 +16,10 @@ export function useThemeSettings(_: string): RawTheme['settings'] {
|
|
|
16
16
|
nextPageLink: { label: 'next page theme settings label' },
|
|
17
17
|
previousPageLink: { label: 'prev page theme settings label' },
|
|
18
18
|
},
|
|
19
|
+
colorMode: {
|
|
20
|
+
modes: ['light', 'dark'],
|
|
21
|
+
default: 'light',
|
|
22
|
+
},
|
|
19
23
|
};
|
|
20
24
|
}
|
|
21
25
|
|