@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.
Files changed (36) hide show
  1. package/ColorModeSwitcher/ColorModeSwitcher.d.ts +2 -0
  2. package/ColorModeSwitcher/ColorModeSwitcher.js +79 -0
  3. package/ColorModeSwitcher/index.d.ts +1 -0
  4. package/ColorModeSwitcher/index.js +17 -0
  5. package/Markdown/MarkdownWrapper.js +1 -1
  6. package/Navbar/Navbar.js +3 -1
  7. package/SourceCode/SourceCode.js +54 -9
  8. package/TableOfContent/TableOfContent.js +7 -18
  9. package/TableOfContent/utils.d.ts +4 -0
  10. package/TableOfContent/utils.js +65 -0
  11. package/globalStyle.d.ts +2 -0
  12. package/globalStyle.js +27 -24
  13. package/icons/ColorModeIcon/ColorModeIcon.d.ts +10 -0
  14. package/icons/ColorModeIcon/ColorModeIcon.js +30 -0
  15. package/icons/ColorModeIcon/index.d.ts +2 -0
  16. package/icons/ColorModeIcon/index.js +5 -0
  17. package/icons/index.d.ts +1 -0
  18. package/icons/index.js +1 -0
  19. package/index.d.ts +1 -0
  20. package/index.js +1 -0
  21. package/mocks/hooks/index.js +4 -0
  22. package/package.json +1 -1
  23. package/src/ColorModeSwitcher/ColorModeSwitcher.tsx +47 -0
  24. package/src/ColorModeSwitcher/index.ts +1 -0
  25. package/src/Markdown/MarkdownWrapper.tsx +24 -0
  26. package/src/Navbar/Navbar.tsx +2 -0
  27. package/src/SourceCode/SourceCode.tsx +16 -5
  28. package/src/TableOfContent/TableOfContent.tsx +11 -18
  29. package/src/TableOfContent/utils.ts +45 -0
  30. package/src/globalStyle.ts +30 -1
  31. package/src/icons/ColorModeIcon/ColorModeIcon.tsx +53 -0
  32. package/src/icons/ColorModeIcon/index.ts +2 -0
  33. package/src/icons/index.ts +1 -0
  34. package/src/index.ts +1 -0
  35. package/src/mocks/hooks/index.ts +4 -0
  36. 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);
@@ -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 _source = source || externalSource?.sample?.get?.(externalSource) || '';
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={source} data-component-name="SourceCode/SourceCode">
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={_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={_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 getDisplayedHeaderIds = () => {
24
- if (!headings) {
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(contentWrapper, getDisplayedHeaderIds());
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
- {headings.map((heading: MdHeading | null, idx: number) => {
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 }) => (depth - 1) * 15}px;
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
+ }
@@ -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
+ `;
@@ -0,0 +1,2 @@
1
+ export { ColorModeIcon } from '@theme/icons/ColorModeIcon/ColorModeIcon';
2
+ export type { ColorModeIconProps } from '@theme/icons/ColorModeIcon/ColorModeIcon';
@@ -1,3 +1,4 @@
1
1
  export * from '@theme/icons/ShelfIcon';
2
2
  export * from '@theme/icons/AlertIcon';
3
3
  export * from '@theme/icons/ArrowIcon';
4
+ export * from '@theme/icons/ColorModeIcon';
package/src/index.ts CHANGED
@@ -15,3 +15,4 @@ export * from './globalStyle';
15
15
  export * from './OperationBadge';
16
16
  export * from './TableOfContent';
17
17
  export * from './Profile';
18
+ export * from './ColorModeSwitcher';
@@ -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
 
@@ -17,3 +17,9 @@ sidebar:
17
17
  hide: false
18
18
  footer:
19
19
  hide: false
20
+ colorMode:
21
+ hide: false
22
+ detect: true
23
+ modes:
24
+ - 'light'
25
+ - 'dark'