@redocly/theme 0.32.0 → 0.32.2

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.
@@ -79,7 +79,8 @@ const StyledCard = styled_components_1.default.div.attrs({ 'data-cy': 'Catalog/C
79
79
  font-size: var(--catalog-card-font-size);
80
80
  font-weight: var(--catalog-card-font-weight);
81
81
  background-color: var(--catalog-card-background-color);
82
- border: var(--catalog-card-border-width) var(--catalog-card-border-style) var(--catalog-card-border-color);
82
+ border: var(--catalog-card-border-width) var(--catalog-card-border-style)
83
+ var(--catalog-card-border-color);
83
84
  transition: all 0.2s ease-in-out;
84
85
  border-radius: var(--catalog-card-border-radius);
85
86
  cursor: pointer;
@@ -98,6 +99,7 @@ const StyledCard = styled_components_1.default.div.attrs({ 'data-cy': 'Catalog/C
98
99
  fill: var(--catalog-card-icon-hover-color);
99
100
  }
100
101
  }
102
+ }
101
103
  `;
102
104
  const CardTitle = styled_components_1.default.h4 `
103
105
  color: var(--catalog-card-title-color);
@@ -7,10 +7,14 @@ export interface CodeBlockProps {
7
7
  header?: CodeBlockControlsProps;
8
8
  dataTestId?: string;
9
9
  className?: string;
10
+ tabs?: FileTabs;
10
11
  withLineNumbers?: boolean;
11
12
  startLineNumber?: number;
12
13
  highlightedHtml?: string;
14
+ skipHighlight?: boolean;
13
15
  codeBlockRef?: (instance: HTMLPreElement | null) => void;
16
+ codeBlockMaxHeight?: string;
17
+ hideCodeColors?: boolean;
14
18
  }
15
19
  export interface Sample {
16
20
  lang: string;
@@ -19,6 +23,13 @@ export interface Sample {
19
23
  export type UnstableExternalCodeSample = Sample & {
20
24
  get: (source: ExternalSource) => string;
21
25
  };
26
+ export type FileTabs = {
27
+ files: {
28
+ name: string;
29
+ }[];
30
+ handleTabSwitch: (name: string) => void;
31
+ activeTabName: string;
32
+ };
22
33
  export interface ExternalSource {
23
34
  sample: UnstableExternalCodeSample;
24
35
  exampleName?: string;
@@ -26,4 +37,4 @@ export interface ExternalSource {
26
37
  properties?: any;
27
38
  operation?: any;
28
39
  }
29
- export declare function CodeBlock({ lang, source, externalSource, header, dataTestId, codeBlockRef, highlightedHtml, withLineNumbers, startLineNumber, className, }: CodeBlockProps): JSX.Element;
40
+ export declare function CodeBlock({ lang, source, externalSource, header, dataTestId, codeBlockRef, highlightedHtml, withLineNumbers, startLineNumber, className, codeBlockMaxHeight, tabs, hideCodeColors, }: CodeBlockProps): JSX.Element;
@@ -32,7 +32,7 @@ const styled_components_1 = __importDefault(require("styled-components"));
32
32
  const utils_1 = require("../../utils");
33
33
  const Feedback_1 = require("../../components/Feedback");
34
34
  const CodeBlock_1 = require("../../components/CodeBlock");
35
- function CodeBlock({ lang, source, externalSource, header, dataTestId = 'source-code', codeBlockRef, highlightedHtml, withLineNumbers, startLineNumber, className, }) {
35
+ function CodeBlock({ lang, source, externalSource, header, dataTestId = 'source-code', codeBlockRef, highlightedHtml, withLineNumbers, startLineNumber, className, codeBlockMaxHeight, tabs, hideCodeColors, }) {
36
36
  var _a;
37
37
  const [sourceCode, setSourceCode] = (0, react_1.useState)(source !== null && source !== void 0 ? source : '');
38
38
  const highlightedCode = highlightedHtml || (0, utils_1.highlight)(sourceCode, lang);
@@ -53,14 +53,18 @@ function CodeBlock({ lang, source, externalSource, header, dataTestId = 'source-
53
53
  controls.copy.data = sourceCode;
54
54
  }
55
55
  return (react_1.default.createElement(Wrapper, { "data-component-name": "CodeBlock/CodeBlock", className: className },
56
- react_1.default.createElement(CodeBlock_1.CodeBlockControls, { className: header === null || header === void 0 ? void 0 : header.className, title: header === null || header === void 0 ? void 0 : header.title, controls: controls }),
57
- react_1.default.createElement(CodeBlock_1.CodeBlockContainer, { ref: codeBlockRef, className: withLineNumbers ? 'line-numbers' : '', dangerouslySetInnerHTML: {
58
- __html: withLineNumbers
59
- ? (0, utils_1.addLineNumbers)(highlightedCode, startLineNumber)
60
- : highlightedCode,
61
- }, "data-cy": dataTestId, withControls: true }),
62
- reportDialog.visible && (react_1.default.createElement(Feedback_1.ReportDialog, Object.assign({}, reportDialog.props, { location: sourceCode, lang: lang })))));
56
+ react_1.default.createElement(CodeBlock_1.CodeBlockControls, { tabs: tabs, className: header === null || header === void 0 ? void 0 : header.className, title: header === null || header === void 0 ? void 0 : header.title, controls: controls }),
57
+ react_1.default.createElement(ContainerWrapper, null,
58
+ react_1.default.createElement(CodeBlock_1.CodeBlockContainer, { ref: codeBlockRef, className: withLineNumbers ? 'line-numbers' : '', dangerouslySetInnerHTML: {
59
+ __html: withLineNumbers
60
+ ? (0, utils_1.addLineNumbers)(highlightedCode, startLineNumber)
61
+ : highlightedCode,
62
+ }, "data-cy": dataTestId, hideCodeColors: hideCodeColors, maxHeight: codeBlockMaxHeight, withControls: true }),
63
+ reportDialog.visible && (react_1.default.createElement(Feedback_1.ReportDialog, Object.assign({}, reportDialog.props, { location: sourceCode, lang: lang }))))));
63
64
  }
64
65
  exports.CodeBlock = CodeBlock;
66
+ const ContainerWrapper = styled_components_1.default.div `
67
+ display: grid; // prevents content to overstretch
68
+ `;
65
69
  const Wrapper = styled_components_1.default.div ``;
66
70
  //# sourceMappingURL=CodeBlock.js.map
@@ -1,3 +1,5 @@
1
1
  export declare const CodeBlockContainer: import("styled-components").StyledComponent<"pre", any, {
2
2
  withControls?: boolean | undefined;
3
+ maxHeight?: string | undefined;
4
+ hideCodeColors?: boolean | undefined;
3
5
  }, never>;
@@ -32,7 +32,6 @@ exports.CodeBlockContainer = styled_components_1.default.pre.attrs(({ className
32
32
  })) `
33
33
  && {
34
34
  overflow-x: auto;
35
- max-height: var(--code-block-max-height);
36
35
  font-family: var(--code-block-font-family);
37
36
  line-height: var(--code-block-line-height);
38
37
  font-weight: var(--code-block-font-weight);
@@ -42,7 +41,7 @@ exports.CodeBlockContainer = styled_components_1.default.pre.attrs(({ className
42
41
  color: var(--code-block-text-color);
43
42
  font-size: var(--code-block-font-size);
44
43
  white-space: var(--code-wrap, pre);
45
- max-height: var(--code-block-max-height, 600px);
44
+ max-height: ${({ maxHeight }) => maxHeight ? maxHeight : 'var(--code-block-max-height, 600px);'};
46
45
  word-break: var(--code-block-word-break, initial);
47
46
 
48
47
  ${(withControls) => withControls
@@ -56,93 +55,106 @@ exports.CodeBlockContainer = styled_components_1.default.pre.attrs(({ className
56
55
  border-radius: var(--code-block-border-radius);
57
56
  border: 1px solid var(--code-block-border-color);
58
57
  `}
59
-
60
- /**
61
- * Based on prism-dark.css
62
- */
63
- code[class*='language-'],
64
- pre[class*='language-'] {
65
- text-shadow: 0 -0.1em 0.2em black;
66
- text-align: left;
67
- word-spacing: normal;
68
- word-break: normal;
69
- word-wrap: normal;
70
- line-height: 1.5;
71
-
72
- -moz-tab-size: 4;
73
- -o-tab-size: 4;
74
- tab-size: 4;
75
-
76
- -webkit-hyphens: none;
77
- -moz-hyphens: none;
78
- -ms-hyphens: none;
79
- hyphens: none;
80
- }
81
-
82
- @media print {
58
+ /**
59
+ * Based on prism-dark.css
60
+ */
83
61
  code[class*='language-'],
84
62
  pre[class*='language-'] {
85
- text-shadow: none;
63
+ text-shadow: 0 -0.1em 0.2em black;
64
+ text-align: left;
65
+ word-spacing: normal;
66
+ word-break: normal;
67
+ word-wrap: normal;
68
+ line-height: 1.5;
69
+
70
+ -moz-tab-size: 4;
71
+ -o-tab-size: 4;
72
+ tab-size: 4;
73
+
74
+ -webkit-hyphens: none;
75
+ -moz-hyphens: none;
76
+ -ms-hyphens: none;
77
+ hyphens: none;
78
+ }
79
+
80
+ @media print {
81
+ code[class*='language-'],
82
+ pre[class*='language-'] {
83
+ text-shadow: none;
84
+ }
85
+ }
86
+
87
+ /* Code blocks */
88
+
89
+ pre[class*='language-'] {
90
+ padding: 1em;
91
+ margin: 0.5em 0;
92
+ overflow: auto;
93
+ }
94
+
95
+ .token.punctuation {
96
+ opacity: 1;
97
+ }
98
+
99
+ .namespace {
100
+ opacity: 0.7;
86
101
  }
87
- }
88
-
89
- /* Code blocks */
90
- pre[class*='language-'] {
91
- padding: 1em;
92
- margin: 0.5em 0;
93
- overflow: auto;
94
- }
95
-
96
- .token.punctuation {
97
- opacity: 1;
98
- }
99
-
100
- .namespace {
101
- opacity: 0.7;
102
- }
103
-
104
- .token.selector,
105
- .token.attr-name,
106
- .token.string,
107
- .token.char,
108
- .token.builtin,
109
- .token.inserted {
110
- & + a,
111
- & + a:visited {
112
- color: var(--code-block-tokens-link-color);
113
- text-decoration: underline;
102
+
103
+ .token.selector,
104
+ .token.attr-name,
105
+ .token.string,
106
+ .token.char,
107
+ .token.builtin,
108
+ .token.inserted {
109
+ & + a,
110
+ & + a:visited {
111
+ color: var(--code-block-tokens-link-color);
112
+ text-decoration: underline;
113
+ }
114
+ }
115
+
116
+ .token.property.string {
117
+ color: var(--code-block-tokens-property-string-color);
114
118
  }
115
- }
116
-
117
- .token.property.string {
118
- color: var(--code-block-tokens-property-string-color);
119
- }
120
-
121
- .token.important,
122
- .token.bold {
123
- font-weight: bold;
124
- }
125
-
126
- .token.italic {
127
- font-style: italic;
128
- }
129
-
130
- .token.entity {
131
- cursor: help;
132
- }
133
-
134
- .code-line {
135
- &:before {
136
- content: attr(data-line-number);
137
- display: inline-block;
138
- min-width: 2em;
139
- padding-right: 0.8em;
140
- text-align: right;
141
- pointer-events: none;
142
- user-select: none;
119
+
120
+ .token.important,
121
+ .token.bold {
122
+ font-weight: bold;
143
123
  }
144
- }
145
124
 
146
- ${(0, utils_1.generateCodeBlockTokens)()}
125
+ .token.italic {
126
+ font-style: italic;
127
+ }
128
+
129
+ .token.entity {
130
+ cursor: help;
131
+ }
132
+
133
+ .code-line {
134
+ &:before {
135
+ content: attr(data-line-number);
136
+ display: inline-block;
137
+ min-width: 2em;
138
+ padding-right: 0.8em;
139
+ text-align: right;
140
+ pointer-events: none;
141
+ user-select: none;
142
+ }
143
+ }
144
+
145
+ .highlighted {
146
+ background: var(--bg-overlay);
147
+ }
148
+
149
+ ${(0, utils_1.generateCodeBlockTokens)()}
150
+
151
+ ${({ hideCodeColors }) => hideCodeColors &&
152
+ (0, styled_components_1.css) `
153
+ .code-line:not(.highlighted),
154
+ .code-line:not(.highlighted) > span,
155
+ .code-line:not(.highlighted) > span > span {
156
+ color: grey;
157
+ }
158
+ `}
147
159
  `;
148
160
  //# sourceMappingURL=CodeBlockContainer.js.map
@@ -1,9 +1,11 @@
1
1
  import React from 'react';
2
+ import type { FileTabs } from '../../components/CodeBlock';
2
3
  export interface CodeBlockControlsProps {
3
4
  children?: React.ReactNode;
4
5
  className?: string;
5
6
  title?: React.ReactNode | string;
6
7
  controls?: ControlItems;
8
+ tabs?: FileTabs;
7
9
  }
8
10
  interface ControlItems {
9
11
  copy?: CopyControlProps;
@@ -29,5 +31,5 @@ interface CopyControlProps extends ControlProps {
29
31
  handleOutside?: boolean;
30
32
  }
31
33
  export type ControlItemType = 'text' | 'icon';
32
- export declare function CodeBlockControls({ children, className, title, controls, }: CodeBlockControlsProps): JSX.Element | null;
34
+ export declare function CodeBlockControls({ children, className, title, controls, tabs, }: CodeBlockControlsProps): JSX.Element | null;
33
35
  export {};
@@ -11,15 +11,27 @@ const CopyButton_1 = require("../../components/CopyButton/CopyButton");
11
11
  const icons_1 = require("../../icons");
12
12
  const hooks_1 = require("../../hooks");
13
13
  const telemetry_1 = require("../../mocks/telemetry");
14
- function CodeBlockControls({ children, className, title, controls, }) {
14
+ const components_1 = require("../../components");
15
+ function CodeBlockControls({ children, className, title, controls, tabs, }) {
15
16
  var _a, _b, _c, _d, _e, _f;
16
17
  const { codeSnippet } = (0, hooks_1.useThemeConfig)();
17
18
  const controlsType = (codeSnippet === null || codeSnippet === void 0 ? void 0 : codeSnippet.elementFormat) || 'icon';
18
19
  const { copy, expand, collapse, select, deselect, report } = controls
19
20
  ? controls
20
21
  : { copy: null, expand: null, collapse: null, select: null, deselect: null, report: null };
22
+ const FileTabs = () => {
23
+ return (react_1.default.createElement(CodeTabsBlock, null, tabs === null || tabs === void 0 ? void 0 : tabs.files.map(({ name }) => {
24
+ const isActive = name === tabs.activeTabName;
25
+ return (react_1.default.createElement(CodeTabButton, { active: isActive, key: name, onClick: () => {
26
+ tabs.handleTabSwitch(name);
27
+ } },
28
+ react_1.default.createElement(icons_1.SmallFileIcon, null),
29
+ name));
30
+ })));
31
+ };
21
32
  const defaultControls = controls ? (react_1.default.createElement(react_1.default.Fragment, null,
22
- react_1.default.createElement(Title, null, title),
33
+ title && react_1.default.createElement(Title, null, title),
34
+ tabs && react_1.default.createElement(FileTabs, null),
23
35
  react_1.default.createElement(ControlsWrapper, null,
24
36
  report && ((_a = report === null || report === void 0 ? void 0 : report.props) === null || _a === void 0 ? void 0 : _a.visible) ? (react_1.default.createElement(CodeBlock_1.CodeBlockControlButton, Object.assign({ "data-cy": "report-button", "data-testid": "report-button", asIcon: controlsType === 'icon', title: (_b = report.props) === null || _b === void 0 ? void 0 : _b.tooltip }, report.props), controlsType === 'icon' ? react_1.default.createElement(icons_1.ReportIcon, null) : ((_c = report.props) === null || _c === void 0 ? void 0 : _c.buttonText) || 'Report')) : null,
25
37
  expand && !((_d = codeSnippet === null || codeSnippet === void 0 ? void 0 : codeSnippet.expand) === null || _d === void 0 ? void 0 : _d.hide) ? (react_1.default.createElement(CodeBlock_1.CodeBlockControlButton, { "data-cy": "expand-all", "data-testid": "expand-all", asIcon: controlsType === 'icon', onClick: expand === null || expand === void 0 ? void 0 : expand.onClick, title: (expand === null || expand === void 0 ? void 0 : expand.tooltipText) || 'Expand all' }, controlsType === 'icon' ? react_1.default.createElement(icons_1.ExpandIcon, null) : (expand === null || expand === void 0 ? void 0 : expand.label) ? expand.label : 'Expand all')) : null,
@@ -60,4 +72,26 @@ const ControlsWrapper = styled_components_1.default.div `
60
72
  opacity: 1;
61
73
  }
62
74
  `;
75
+ const CodeTabsBlock = styled_components_1.default.div `
76
+ background: transparent;
77
+ border-radius: var(--global-border-radius) 0;
78
+ display: flex;
79
+ flex-wrap: wrap;
80
+ margin: 12px 0;
81
+ justify-content: flex-start;
82
+ flex-direction: row;
83
+ padding: 0 12px;
84
+ `;
85
+ const CodeTabButton = (0, styled_components_1.default)(components_1.Button) `
86
+ border: none;
87
+ padding: 8px 12px;
88
+ background: ${({ active }) => (active ? 'var(--color-primary-base)' : 'transparent')};
89
+ color: ${({ active }) => (active ? '#fff' : 'var(--text-base)')};
90
+ font-size: var(--font-size-sm);
91
+
92
+ &:hover {
93
+ background: var(--color-primary-bg-hover);
94
+ border: none;
95
+ }
96
+ `;
63
97
  //# sourceMappingURL=CodeBlockControls.js.map
@@ -39,6 +39,7 @@ const TextContainer = styled_components_1.default.div `
39
39
  display: flex;
40
40
  flex-direction: column;
41
41
  gap: var(--spacing-unit);
42
+ width: 100%;
42
43
  `;
43
44
  const Heading = styled_components_1.default.div `
44
45
  letter-spacing: var(--admonition-heading-letter-spacing);
@@ -1,4 +1,4 @@
1
- export declare const CompactTypography: import("styled-components").StyledComponent<"div", any, import("../../components/Typography/Typography").TypographyProps & {
1
+ export declare const CompactTypography: import("styled-components").StyledComponent<"span", any, import("../../components/Typography/Typography").TypographyProps & {
2
2
  mt: string;
3
3
  mb: string;
4
4
  'data-component-name': string;
@@ -10,4 +10,4 @@ export interface TypographyProps {
10
10
  marginTop?: string;
11
11
  textAlign?: string;
12
12
  }
13
- export declare const Typography: import("styled-components").StyledComponent<"div", any, TypographyProps, never>;
13
+ export declare const Typography: import("styled-components").StyledComponent<"span", any, TypographyProps, never>;
@@ -5,7 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.Typography = void 0;
7
7
  const styled_components_1 = __importDefault(require("styled-components"));
8
- exports.Typography = styled_components_1.default.div.attrs(({ className }) => ({
8
+ exports.Typography = styled_components_1.default.span.attrs(({ className }) => ({
9
9
  'data-component-name': 'Typography/Typography',
10
10
  className,
11
11
  })) `
@@ -3,4 +3,5 @@ interface IconProps {
3
3
  className?: string;
4
4
  }
5
5
  export declare const FileIcon: import("styled-components").StyledComponent<({ className }: IconProps) => React.JSX.Element, any, {}, never>;
6
+ export declare const SmallFileIcon: import("styled-components").StyledComponent<({ className }: IconProps) => React.JSX.Element, any, {}, never>;
6
7
  export {};
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.FileIcon = void 0;
6
+ exports.SmallFileIcon = exports.FileIcon = void 0;
7
7
  const react_1 = __importDefault(require("react"));
8
8
  const styled_components_1 = __importDefault(require("styled-components"));
9
9
  const Icon = ({ className }) => (react_1.default.createElement("svg", { "data-component-name": "icons/FileIcon", viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", className: className },
@@ -14,4 +14,8 @@ exports.FileIcon = (0, styled_components_1.default)(Icon) `
14
14
  height: 1.3em;
15
15
  vertical-align: middle;
16
16
  `;
17
+ exports.SmallFileIcon = (0, styled_components_1.default)(Icon) `
18
+ width: 1em;
19
+ height: 1em;
20
+ `;
17
21
  //# sourceMappingURL=FileIcon.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redocly/theme",
3
- "version": "0.32.0",
3
+ "version": "0.32.2",
4
4
  "description": "Shared UI components lib",
5
5
  "keywords": [
6
6
  "theme",
@@ -28,57 +28,57 @@
28
28
  "styled-system": "^5.1.5"
29
29
  },
30
30
  "devDependencies": {
31
- "@storybook/addon-actions": "^7.5.0",
32
- "@storybook/addon-essentials": "^7.5.0",
33
- "@storybook/addon-interactions": "^7.5.0",
34
- "@storybook/addon-links": "^7.5.0",
35
- "@storybook/addon-viewport": "^7.5.0",
36
- "@storybook/addons": "^7.5.0",
37
- "@storybook/core-common": "^7.5.0",
38
- "@storybook/node-logger": "^7.5.0",
39
- "@storybook/react": "^7.5.0",
40
- "@storybook/react-webpack5": "^7.5.0",
41
- "@storybook/testing-library": "^0.2.2",
42
- "@storybook/theming": "^7.5.0",
43
- "@testing-library/jest-dom": "^6.1.4",
44
- "@testing-library/react": "^14.0.0",
45
- "@testing-library/user-event": "^14.5.1",
46
- "@types/highlight-words-core": "^1.2.1",
47
- "@types/jest": "^29.2.1",
48
- "@types/jest-when": "^3.5.2",
49
- "@types/lodash.throttle": "^4.1.7",
50
- "@types/node": "^18.11.18",
51
- "@types/prismjs": "^1.26.0",
52
- "@types/react": "^18.2.31",
53
- "@types/react-dom": "^18.2.14",
54
- "@types/styled-components": "^5.1.30",
55
- "@types/styled-system": "^5.1.13",
56
- "@typescript-eslint/eslint-plugin": "^5.23.0",
57
- "@typescript-eslint/parser": "^5.23.0",
58
- "chromatic": "^6.10.2",
59
- "concurrently": "^7.4.0",
60
- "jest": "^29.2.2",
61
- "jest-environment-jsdom": "^29.2.2",
62
- "jest-styled-components": "^7.1.1",
63
- "jest-when": "^3.5.1",
64
- "json-schema-to-ts": "^2.7.2",
65
- "lodash.throttle": "^4.1.1",
66
- "npm-run-all": "^4.1.5",
67
- "react-refresh": "^0.14.0",
68
- "react-router-dom": "^6.10.0",
69
- "storybook": "^7.5.0",
70
- "storybook-addon-pseudo-states": "^2.1.2",
31
+ "@storybook/addon-actions": "7.6.4",
32
+ "@storybook/addon-essentials": "7.6.4",
33
+ "@storybook/addon-interactions": "7.6.4",
34
+ "@storybook/addon-links": "7.6.4",
35
+ "@storybook/addon-viewport": "7.6.4",
36
+ "@storybook/addons": "7.6.4",
37
+ "@storybook/core-common": "7.6.4",
38
+ "@storybook/node-logger": "7.6.4",
39
+ "@storybook/react": "7.6.4",
40
+ "@storybook/react-webpack5": "7.6.4",
41
+ "@storybook/testing-library": "0.2.2",
42
+ "@storybook/theming": "7.6.4",
43
+ "@testing-library/jest-dom": "6.1.5",
44
+ "@testing-library/react": "14.1.2",
45
+ "@testing-library/user-event": "14.5.1",
46
+ "@types/highlight-words-core": "1.2.3",
47
+ "@types/jest": "29.5.11",
48
+ "@types/jest-when": "3.5.5",
49
+ "@types/lodash.throttle": "4.1.9",
50
+ "@types/node": "18.19.3",
51
+ "@types/prismjs": "1.26.3",
52
+ "@types/react": "18.2.45",
53
+ "@types/react-dom": "18.2.17",
54
+ "@types/styled-components": "5.1.34",
55
+ "@types/styled-system": "5.1.22",
56
+ "@typescript-eslint/eslint-plugin": "5.55.0",
57
+ "@typescript-eslint/parser": "5.55.0",
58
+ "chromatic": "6.17.2",
59
+ "concurrently": "7.6.0",
60
+ "jest": "29.5.0",
61
+ "jest-environment-jsdom": "29.5.0",
62
+ "jest-styled-components": "7.2.0",
63
+ "jest-when": "3.6.0",
64
+ "json-schema-to-ts": "2.7.2",
65
+ "lodash.throttle": "4.1.1",
66
+ "npm-run-all": "4.1.5",
67
+ "react-refresh": "0.14.0",
68
+ "react-router-dom": "6.10.0",
69
+ "storybook": "7.6.4",
70
+ "storybook-addon-pseudo-states": "2.1.2",
71
71
  "storybook-design-token": "3.0.0-beta.6",
72
- "styled-components": "^5.3.11",
73
- "styled-system": "^5.1.5",
74
- "ts-jest": "^29.0.3",
75
- "ts-node": "^10.7.0",
76
- "ts-node-dev": "^2.0.0",
77
- "tsc-alias": "^1.8.2",
78
- "tsconfig-paths": "^4.2.0",
79
- "tsconfig-paths-webpack-plugin": "^3.5.2",
80
- "typescript": "^5.2.2",
81
- "webpack": "^5.72.0",
72
+ "styled-components": "5.3.11",
73
+ "styled-system": "5.1.5",
74
+ "ts-jest": "29.1.1",
75
+ "ts-node": "10.9.1",
76
+ "ts-node-dev": "2.0.0",
77
+ "tsc-alias": "1.8.3",
78
+ "tsconfig-paths": "4.2.0",
79
+ "tsconfig-paths-webpack-plugin": "3.5.2",
80
+ "typescript": "5.2.2",
81
+ "webpack": "5.88.2",
82
82
  "@redocly/portal-types": "1.2.1"
83
83
  },
84
84
  "dependencies": {
@@ -76,7 +76,8 @@ const StyledCard = styled.div.attrs({ 'data-cy': 'Catalog/CatalogCard' })`
76
76
  font-size: var(--catalog-card-font-size);
77
77
  font-weight: var(--catalog-card-font-weight);
78
78
  background-color: var(--catalog-card-background-color);
79
- border: var(--catalog-card-border-width) var(--catalog-card-border-style) var(--catalog-card-border-color);
79
+ border: var(--catalog-card-border-width) var(--catalog-card-border-style)
80
+ var(--catalog-card-border-color);
80
81
  transition: all 0.2s ease-in-out;
81
82
  border-radius: var(--catalog-card-border-radius);
82
83
  cursor: pointer;
@@ -95,6 +96,7 @@ const StyledCard = styled.div.attrs({ 'data-cy': 'Catalog/CatalogCard' })`
95
96
  fill: var(--catalog-card-icon-hover-color);
96
97
  }
97
98
  }
99
+ }
98
100
  `;
99
101
 
100
102
  const CardTitle = styled.h4`
@@ -14,11 +14,14 @@ export interface CodeBlockProps {
14
14
  header?: CodeBlockControlsProps;
15
15
  dataTestId?: string;
16
16
  className?: string;
17
-
17
+ tabs?: FileTabs;
18
18
  withLineNumbers?: boolean;
19
19
  startLineNumber?: number;
20
20
  highlightedHtml?: string;
21
+ skipHighlight?: boolean;
21
22
  codeBlockRef?: (instance: HTMLPreElement | null) => void;
23
+ codeBlockMaxHeight?: string;
24
+ hideCodeColors?: boolean;
22
25
  }
23
26
 
24
27
  export interface Sample {
@@ -30,6 +33,12 @@ export type UnstableExternalCodeSample = Sample & {
30
33
  get: (source: ExternalSource) => string;
31
34
  };
32
35
 
36
+ export type FileTabs = {
37
+ files: { name: string }[];
38
+ handleTabSwitch: (name: string) => void;
39
+ activeTabName: string;
40
+ };
41
+
33
42
  export interface ExternalSource {
34
43
  sample: UnstableExternalCodeSample;
35
44
  exampleName?: string;
@@ -49,6 +58,9 @@ export function CodeBlock({
49
58
  withLineNumbers,
50
59
  startLineNumber,
51
60
  className,
61
+ codeBlockMaxHeight,
62
+ tabs,
63
+ hideCodeColors,
52
64
  }: CodeBlockProps): JSX.Element {
53
65
  const [sourceCode, setSourceCode] = useState<string>(source ?? '');
54
66
 
@@ -78,27 +90,40 @@ export function CodeBlock({
78
90
 
79
91
  return (
80
92
  <Wrapper data-component-name="CodeBlock/CodeBlock" className={className}>
81
- <CodeBlockControls className={header?.className} title={header?.title} controls={controls} />
82
- <CodeBlockContainer
83
- ref={codeBlockRef}
84
- className={withLineNumbers ? 'line-numbers' : ''}
85
- dangerouslySetInnerHTML={{
86
- __html: withLineNumbers
87
- ? addLineNumbers(highlightedCode, startLineNumber)
88
- : highlightedCode,
89
- }}
90
- data-cy={dataTestId}
91
- withControls={true}
93
+ <CodeBlockControls
94
+ tabs={tabs}
95
+ className={header?.className}
96
+ title={header?.title}
97
+ controls={controls}
92
98
  />
93
- {reportDialog.visible && (
94
- <ReportDialog
95
- {...(reportDialog.props as ReportDialogProps)}
96
- location={sourceCode}
97
- lang={lang}
99
+ <ContainerWrapper>
100
+ <CodeBlockContainer
101
+ ref={codeBlockRef}
102
+ className={withLineNumbers ? 'line-numbers' : ''}
103
+ dangerouslySetInnerHTML={{
104
+ __html: withLineNumbers
105
+ ? addLineNumbers(highlightedCode, startLineNumber)
106
+ : highlightedCode,
107
+ }}
108
+ data-cy={dataTestId}
109
+ hideCodeColors={hideCodeColors}
110
+ maxHeight={codeBlockMaxHeight}
111
+ withControls={true}
98
112
  />
99
- )}
113
+ {reportDialog.visible && (
114
+ <ReportDialog
115
+ {...(reportDialog.props as ReportDialogProps)}
116
+ location={sourceCode}
117
+ lang={lang}
118
+ />
119
+ )}
120
+ </ContainerWrapper>
100
121
  </Wrapper>
101
122
  );
102
123
  }
103
124
 
125
+ const ContainerWrapper = styled.div`
126
+ display: grid; // prevents content to overstretch
127
+ `;
128
+
104
129
  const Wrapper = styled.div``;
@@ -5,10 +5,9 @@ import { generateCodeBlockTokens } from '@theme/utils';
5
5
  export const CodeBlockContainer = styled.pre.attrs<{ className?: string }>(({ className }) => ({
6
6
  'data-component-name': 'CodeBlock/CodeBlockContainer',
7
7
  className,
8
- }))<{ withControls?: boolean }>`
8
+ }))<{ withControls?: boolean; maxHeight?: string; hideCodeColors?: boolean }>`
9
9
  && {
10
10
  overflow-x: auto;
11
- max-height: var(--code-block-max-height);
12
11
  font-family: var(--code-block-font-family);
13
12
  line-height: var(--code-block-line-height);
14
13
  font-weight: var(--code-block-font-weight);
@@ -18,7 +17,8 @@ export const CodeBlockContainer = styled.pre.attrs<{ className?: string }>(({ cl
18
17
  color: var(--code-block-text-color);
19
18
  font-size: var(--code-block-font-size);
20
19
  white-space: var(--code-wrap, pre);
21
- max-height: var(--code-block-max-height, 600px);
20
+ max-height: ${({ maxHeight }) =>
21
+ maxHeight ? maxHeight : 'var(--code-block-max-height, 600px);'};
22
22
  word-break: var(--code-block-word-break, initial);
23
23
 
24
24
  ${(withControls) =>
@@ -33,92 +33,106 @@ export const CodeBlockContainer = styled.pre.attrs<{ className?: string }>(({ cl
33
33
  border-radius: var(--code-block-border-radius);
34
34
  border: 1px solid var(--code-block-border-color);
35
35
  `}
36
-
37
- /**
38
- * Based on prism-dark.css
39
- */
40
- code[class*='language-'],
41
- pre[class*='language-'] {
42
- text-shadow: 0 -0.1em 0.2em black;
43
- text-align: left;
44
- word-spacing: normal;
45
- word-break: normal;
46
- word-wrap: normal;
47
- line-height: 1.5;
48
-
49
- -moz-tab-size: 4;
50
- -o-tab-size: 4;
51
- tab-size: 4;
52
-
53
- -webkit-hyphens: none;
54
- -moz-hyphens: none;
55
- -ms-hyphens: none;
56
- hyphens: none;
57
- }
58
-
59
- @media print {
36
+ /**
37
+ * Based on prism-dark.css
38
+ */
60
39
  code[class*='language-'],
61
40
  pre[class*='language-'] {
62
- text-shadow: none;
41
+ text-shadow: 0 -0.1em 0.2em black;
42
+ text-align: left;
43
+ word-spacing: normal;
44
+ word-break: normal;
45
+ word-wrap: normal;
46
+ line-height: 1.5;
47
+
48
+ -moz-tab-size: 4;
49
+ -o-tab-size: 4;
50
+ tab-size: 4;
51
+
52
+ -webkit-hyphens: none;
53
+ -moz-hyphens: none;
54
+ -ms-hyphens: none;
55
+ hyphens: none;
56
+ }
57
+
58
+ @media print {
59
+ code[class*='language-'],
60
+ pre[class*='language-'] {
61
+ text-shadow: none;
62
+ }
63
+ }
64
+
65
+ /* Code blocks */
66
+
67
+ pre[class*='language-'] {
68
+ padding: 1em;
69
+ margin: 0.5em 0;
70
+ overflow: auto;
71
+ }
72
+
73
+ .token.punctuation {
74
+ opacity: 1;
63
75
  }
64
- }
65
-
66
- /* Code blocks */
67
- pre[class*='language-'] {
68
- padding: 1em;
69
- margin: 0.5em 0;
70
- overflow: auto;
71
- }
72
-
73
- .token.punctuation {
74
- opacity: 1;
75
- }
76
-
77
- .namespace {
78
- opacity: 0.7;
79
- }
80
-
81
- .token.selector,
82
- .token.attr-name,
83
- .token.string,
84
- .token.char,
85
- .token.builtin,
86
- .token.inserted {
87
- & + a,
88
- & + a:visited {
89
- color: var(--code-block-tokens-link-color);
90
- text-decoration: underline;
76
+
77
+ .namespace {
78
+ opacity: 0.7;
79
+ }
80
+
81
+ .token.selector,
82
+ .token.attr-name,
83
+ .token.string,
84
+ .token.char,
85
+ .token.builtin,
86
+ .token.inserted {
87
+ & + a,
88
+ & + a:visited {
89
+ color: var(--code-block-tokens-link-color);
90
+ text-decoration: underline;
91
+ }
91
92
  }
92
- }
93
-
94
- .token.property.string {
95
- color: var(--code-block-tokens-property-string-color);
96
- }
97
-
98
- .token.important,
99
- .token.bold {
100
- font-weight: bold;
101
- }
102
-
103
- .token.italic {
104
- font-style: italic;
105
- }
106
-
107
- .token.entity {
108
- cursor: help;
109
- }
110
-
111
- .code-line {
112
- &:before {
113
- content: attr(data-line-number);
114
- display: inline-block;
115
- min-width: 2em;
116
- padding-right: 0.8em;
117
- text-align: right;
118
- pointer-events: none;
119
- user-select: none;
93
+
94
+ .token.property.string {
95
+ color: var(--code-block-tokens-property-string-color);
96
+ }
97
+
98
+ .token.important,
99
+ .token.bold {
100
+ font-weight: bold;
101
+ }
102
+
103
+ .token.italic {
104
+ font-style: italic;
105
+ }
106
+
107
+ .token.entity {
108
+ cursor: help;
109
+ }
110
+
111
+ .code-line {
112
+ &:before {
113
+ content: attr(data-line-number);
114
+ display: inline-block;
115
+ min-width: 2em;
116
+ padding-right: 0.8em;
117
+ text-align: right;
118
+ pointer-events: none;
119
+ user-select: none;
120
+ }
121
+ }
122
+
123
+ .highlighted {
124
+ background: var(--bg-overlay);
120
125
  }
121
- }
122
126
 
123
- ${generateCodeBlockTokens()}
127
+ ${generateCodeBlockTokens()}
128
+
129
+ ${({ hideCodeColors }) =>
130
+ hideCodeColors &&
131
+ css`
132
+ .code-line:not(.highlighted),
133
+ .code-line:not(.highlighted) > span,
134
+ .code-line:not(.highlighted) > span > span {
135
+ color: grey;
136
+ }
137
+ `}
124
138
  `;
@@ -3,15 +3,25 @@ import styled from 'styled-components';
3
3
 
4
4
  import { CodeBlockControlButton } from '@theme/components/CodeBlock';
5
5
  import { CopyButton } from '@theme/components/CopyButton/CopyButton';
6
- import { CollapseIcon, DeselectIcon, ExpandIcon, ReportIcon, SelectIcon } from '@theme/icons';
6
+ import {
7
+ CollapseIcon,
8
+ DeselectIcon,
9
+ ExpandIcon,
10
+ ReportIcon,
11
+ SelectIcon,
12
+ SmallFileIcon,
13
+ } from '@theme/icons';
7
14
  import { useThemeConfig } from '@theme/hooks';
8
15
  import { telemetry } from '@portal/telemetry';
16
+ import { Button } from '@theme/components';
17
+ import type { FileTabs } from '@theme/components/CodeBlock';
9
18
 
10
19
  export interface CodeBlockControlsProps {
11
20
  children?: React.ReactNode;
12
21
  className?: string;
13
22
  title?: React.ReactNode | string;
14
23
  controls?: ControlItems;
24
+ tabs?: FileTabs;
15
25
  }
16
26
 
17
27
  interface ControlItems {
@@ -47,6 +57,7 @@ export function CodeBlockControls({
47
57
  className,
48
58
  title,
49
59
  controls,
60
+ tabs,
50
61
  }: CodeBlockControlsProps): JSX.Element | null {
51
62
  const { codeSnippet } = useThemeConfig();
52
63
  const controlsType = (codeSnippet?.elementFormat as ControlItemType) || 'icon';
@@ -54,9 +65,32 @@ export function CodeBlockControls({
54
65
  ? controls
55
66
  : { copy: null, expand: null, collapse: null, select: null, deselect: null, report: null };
56
67
 
68
+ const FileTabs = () => {
69
+ return (
70
+ <CodeTabsBlock>
71
+ {tabs?.files.map(({ name }) => {
72
+ const isActive = name === tabs.activeTabName;
73
+ return (
74
+ <CodeTabButton
75
+ active={isActive}
76
+ key={name}
77
+ onClick={() => {
78
+ tabs.handleTabSwitch(name);
79
+ }}
80
+ >
81
+ <SmallFileIcon />
82
+ {name}
83
+ </CodeTabButton>
84
+ );
85
+ })}
86
+ </CodeTabsBlock>
87
+ );
88
+ };
89
+
57
90
  const defaultControls = controls ? (
58
91
  <>
59
- <Title>{title}</Title>
92
+ {title && <Title>{title}</Title>}
93
+ {tabs && <FileTabs />}
60
94
  <ControlsWrapper>
61
95
  {report && report?.props?.visible ? (
62
96
  <CodeBlockControlButton
@@ -186,3 +220,27 @@ const ControlsWrapper = styled.div`
186
220
  opacity: 1;
187
221
  }
188
222
  `;
223
+
224
+ const CodeTabsBlock = styled.div`
225
+ background: transparent;
226
+ border-radius: var(--global-border-radius) 0;
227
+ display: flex;
228
+ flex-wrap: wrap;
229
+ margin: 12px 0;
230
+ justify-content: flex-start;
231
+ flex-direction: row;
232
+ padding: 0 12px;
233
+ `;
234
+
235
+ const CodeTabButton = styled(Button)<{ active: boolean }>`
236
+ border: none;
237
+ padding: 8px 12px;
238
+ background: ${({ active }) => (active ? 'var(--color-primary-base)' : 'transparent')};
239
+ color: ${({ active }) => (active ? '#fff' : 'var(--text-base)')};
240
+ font-size: var(--font-size-sm);
241
+
242
+ &:hover {
243
+ background: var(--color-primary-bg-hover);
244
+ border: none;
245
+ }
246
+ `;
@@ -64,6 +64,7 @@ const TextContainer = styled.div`
64
64
  display: flex;
65
65
  flex-direction: column;
66
66
  gap: var(--spacing-unit);
67
+ width: 100%;
67
68
  `;
68
69
 
69
70
  const Heading = styled.div<AdmonitionTypeProps>`
@@ -13,7 +13,7 @@ export interface TypographyProps {
13
13
  textAlign?: string;
14
14
  }
15
15
 
16
- export const Typography = styled.div.attrs<{ className?: string }>(({ className }) => ({
16
+ export const Typography = styled.span.attrs<{ className?: string }>(({ className }) => ({
17
17
  'data-component-name': 'Typography/Typography',
18
18
  className,
19
19
  }))<TypographyProps>`
@@ -26,3 +26,8 @@ export const FileIcon = styled(Icon)`
26
26
  height: 1.3em;
27
27
  vertical-align: middle;
28
28
  `;
29
+
30
+ export const SmallFileIcon = styled(Icon)`
31
+ width: 1em;
32
+ height: 1em;
33
+ `;