@redocly/theme 0.71.0-next.5 → 0.71.0-next.7

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 (65) hide show
  1. package/lib/components/Feedback/Mood.js +1 -1
  2. package/lib/components/Feedback/Rating.js +1 -1
  3. package/lib/components/Feedback/Scale.js +1 -1
  4. package/lib/components/Feedback/Sentiment.js +1 -1
  5. package/lib/components/Filter/Filter.js +1 -1
  6. package/lib/components/MCP/McpConnectionTabs.d.ts +1 -1
  7. package/lib/components/MCP/McpConnectionTabs.js +24 -1
  8. package/lib/components/MissingTranslationNotice/MissingTranslationNotice.d.ts +11 -0
  9. package/lib/components/MissingTranslationNotice/MissingTranslationNotice.js +83 -0
  10. package/lib/components/MissingTranslationNotice/variables.d.ts +1 -0
  11. package/lib/components/MissingTranslationNotice/variables.dark.d.ts +1 -0
  12. package/lib/components/MissingTranslationNotice/variables.dark.js +10 -0
  13. package/lib/components/MissingTranslationNotice/variables.js +29 -0
  14. package/lib/components/PageActions/PageActions.js +4 -2
  15. package/lib/components/PageActions/variables.js +2 -1
  16. package/lib/components/Select/Select.js +1 -2
  17. package/lib/core/hooks/use-active-section-id.js +8 -0
  18. package/lib/core/hooks/use-page-actions.d.ts +1 -1
  19. package/lib/core/hooks/use-page-actions.js +18 -2
  20. package/lib/core/styles/dark.js +4 -2
  21. package/lib/core/styles/global.js +70 -68
  22. package/lib/core/types/hooks.d.ts +2 -5
  23. package/lib/core/types/l10n.d.ts +2 -1
  24. package/lib/core/utils/mcp.d.ts +1 -0
  25. package/lib/core/utils/mcp.js +4 -0
  26. package/lib/icons/McpIcon/McpIcon.d.ts +25 -0
  27. package/lib/icons/McpIcon/McpIcon.js +24 -0
  28. package/lib/icons/TranslateIcon/TranslateIcon.d.ts +25 -0
  29. package/lib/icons/TranslateIcon/TranslateIcon.js +21 -0
  30. package/lib/index.d.ts +2 -0
  31. package/lib/index.js +3 -0
  32. package/lib/layouts/CodeWalkthroughLayout.js +9 -1
  33. package/lib/layouts/DocumentationLayout.js +2 -0
  34. package/lib/markdoc/components/Heading/Heading.js +9 -0
  35. package/package.json +3 -3
  36. package/src/components/CatalogClassic/CatalogClassicInfoBlock.tsx +1 -1
  37. package/src/components/Feedback/Mood.tsx +1 -1
  38. package/src/components/Feedback/Rating.tsx +1 -1
  39. package/src/components/Feedback/Scale.tsx +1 -1
  40. package/src/components/Feedback/Sentiment.tsx +1 -1
  41. package/src/components/Filter/Filter.tsx +2 -2
  42. package/src/components/MCP/McpConnectionTabs.tsx +56 -2
  43. package/src/components/MissingTranslationNotice/MissingTranslationNotice.tsx +113 -0
  44. package/src/components/MissingTranslationNotice/variables.dark.ts +7 -0
  45. package/src/components/MissingTranslationNotice/variables.ts +26 -0
  46. package/src/components/PageActions/PageActions.tsx +5 -2
  47. package/src/components/PageActions/variables.ts +2 -1
  48. package/src/components/Search/FilterFields/SearchFilterFieldSelect.tsx +1 -1
  49. package/src/components/Select/Select.tsx +1 -2
  50. package/src/core/hooks/__mocks__/use-theme-hooks.ts +1 -1
  51. package/src/core/hooks/use-active-section-id.ts +13 -0
  52. package/src/core/hooks/use-codeblock-tabs-controls.ts +1 -1
  53. package/src/core/hooks/use-page-actions.ts +27 -4
  54. package/src/core/styles/dark.ts +2 -0
  55. package/src/core/styles/global.ts +2 -0
  56. package/src/core/types/hooks.ts +2 -2
  57. package/src/core/types/l10n.ts +12 -2
  58. package/src/core/utils/mcp.ts +4 -0
  59. package/src/icons/McpIcon/McpIcon.tsx +37 -0
  60. package/src/icons/TranslateIcon/TranslateIcon.tsx +22 -0
  61. package/src/index.ts +3 -0
  62. package/src/layouts/CodeWalkthroughLayout.tsx +11 -1
  63. package/src/layouts/DocumentationLayout.tsx +2 -0
  64. package/src/markdoc/components/Heading/Heading.tsx +10 -1
  65. package/src/markdoc/tags/code-walkthrough.ts +1 -1
@@ -0,0 +1,113 @@
1
+ import React from 'react';
2
+ import styled from 'styled-components';
3
+
4
+ import type { JSX } from 'react';
5
+
6
+ import { CloseIcon } from '@redocly/theme/icons/CloseIcon/CloseIcon';
7
+ import { TranslateIcon } from '@redocly/theme/icons/TranslateIcon/TranslateIcon';
8
+ import { Markdown } from '@redocly/theme/components/Markdown/Markdown';
9
+ import { useThemeHooks } from '@redocly/theme/core/hooks';
10
+
11
+ export type MissingTranslationNoticeProps = {
12
+ className?: string;
13
+ };
14
+
15
+ /**
16
+ * Tells the reader that the page shows the original language,
17
+ * because the translation for the selected locale is missing.
18
+ * Renders only on fallback pages of a locale that sets `showMissingTranslationNotice`.
19
+ * The reader can close the notice for the current page.
20
+ */
21
+ export function MissingTranslationNotice({
22
+ className,
23
+ }: MissingTranslationNoticeProps): JSX.Element | null {
24
+ const { useTranslate, useMarkdownText, usePageProps, useL10nConfig } = useThemeHooks();
25
+ const { translate } = useTranslate();
26
+ const { isTranslationFallback } = usePageProps<{ isTranslationFallback?: boolean }>();
27
+ const { currentLocale, defaultLocale, locales } = useL10nConfig();
28
+ const [isDismissed, setIsDismissed] = React.useState(false);
29
+
30
+ const isShown =
31
+ isTranslationFallback === true &&
32
+ currentLocale !== defaultLocale &&
33
+ locales.some(
34
+ ({ code, showMissingTranslationNotice }) =>
35
+ code === currentLocale && showMissingTranslationNotice === true,
36
+ );
37
+ const language = locales.find(({ code }) => code === defaultLocale)?.name || defaultLocale;
38
+ const content = useMarkdownText(
39
+ isShown ? translate('missingTranslationNotice.content', { language }) : '',
40
+ );
41
+
42
+ if (!isShown || isDismissed) {
43
+ return null;
44
+ }
45
+
46
+ return (
47
+ <MissingTranslationNoticeWrapper
48
+ className={className}
49
+ data-component-name="MissingTranslationNotice/MissingTranslationNotice"
50
+ >
51
+ <Icon color="--missing-translation-notice-icon-color" />
52
+ <Content>
53
+ <Title>
54
+ {translate('missingTranslationNotice.title', {
55
+ defaultValue: 'This page has not yet been translated',
56
+ })}
57
+ </Title>
58
+ <Markdown compact>{content}</Markdown>
59
+ </Content>
60
+ <CloseButton
61
+ type="button"
62
+ aria-label={translate('missingTranslationNotice.close', { defaultValue: 'Close' })}
63
+ onClick={() => setIsDismissed(true)}
64
+ >
65
+ <CloseIcon color="--missing-translation-notice-text-color" />
66
+ </CloseButton>
67
+ </MissingTranslationNoticeWrapper>
68
+ );
69
+ }
70
+
71
+ const MissingTranslationNoticeWrapper = styled.div`
72
+ display: flex;
73
+ flex-direction: row;
74
+ align-items: flex-start;
75
+ gap: var(--missing-translation-notice-gap);
76
+
77
+ padding: var(--missing-translation-notice-padding);
78
+ margin: var(--missing-translation-notice-margin-vertical) 0;
79
+ color: var(--missing-translation-notice-text-color);
80
+ background-color: var(--missing-translation-notice-bg-color);
81
+ border: 1px solid var(--missing-translation-notice-border-color);
82
+ border-left: var(--missing-translation-notice-accent-width) solid
83
+ var(--missing-translation-notice-accent-color);
84
+ border-radius: var(--missing-translation-notice-border-radius);
85
+ `;
86
+
87
+ const Icon = styled(TranslateIcon)`
88
+ flex-shrink: 0;
89
+ `;
90
+
91
+ const Content = styled.div`
92
+ display: flex;
93
+ flex-direction: column;
94
+ flex: 1;
95
+ gap: var(--spacing-xxs);
96
+
97
+ font-size: var(--missing-translation-notice-font-size);
98
+ line-height: var(--missing-translation-notice-line-height);
99
+ `;
100
+
101
+ const Title = styled.span`
102
+ font-size: var(--missing-translation-notice-title-font-size);
103
+ font-weight: var(--missing-translation-notice-title-font-weight);
104
+ `;
105
+
106
+ const CloseButton = styled.button`
107
+ display: flex;
108
+ flex-shrink: 0;
109
+ padding: 0;
110
+ border: none;
111
+ background: none;
112
+ cursor: pointer;
113
+ `;
@@ -0,0 +1,7 @@
1
+ import { css } from 'styled-components';
2
+
3
+ export const missingTranslationNoticeDarkMode = css`
4
+ --missing-translation-notice-bg-color: var(--color-warm-grey-3); // @presenter Color
5
+ --missing-translation-notice-icon-color: var(--color-blue-4); // @presenter Color
6
+ --missing-translation-notice-accent-color: var(--color-blue-4); // @presenter Color
7
+ `;
@@ -0,0 +1,26 @@
1
+ import { css } from 'styled-components';
2
+
3
+ export const missingTranslationNotice = css`
4
+ /**
5
+ * @tokens Missing translation notice
6
+ */
7
+
8
+ --missing-translation-notice-bg-color: var(--color-warm-grey-1); // @presenter Color
9
+ --missing-translation-notice-text-color: var(--text-color-primary); // @presenter Color
10
+ --missing-translation-notice-icon-color: var(--color-blue-6); // @presenter Color
11
+ --missing-translation-notice-border-color: var(--border-color-secondary); // @presenter Color
12
+ --missing-translation-notice-accent-color: var(--color-blue-6); // @presenter Color
13
+
14
+ --missing-translation-notice-accent-width: 3px; // @presenter Spacing
15
+ --missing-translation-notice-border-radius: var(--border-radius-lg); // @presenter BorderRadius
16
+ --missing-translation-notice-padding: var(--spacing-sm) var(--spacing-base); // @presenter Spacing
17
+ --missing-translation-notice-margin-vertical: var(--spacing-base); // @presenter Spacing
18
+ --missing-translation-notice-gap: var(--spacing-sm); // @presenter Spacing
19
+
20
+ --missing-translation-notice-title-font-size: var(--font-size-base); // @presenter FontSize
21
+ --missing-translation-notice-title-font-weight: var(--font-weight-semibold); // @presenter FontWeight
22
+ --missing-translation-notice-font-size: var(--font-size-base); // @presenter FontSize
23
+ --missing-translation-notice-line-height: var(--line-height-base); // @presenter LineHeight
24
+
25
+ // @tokens End
26
+ `;
@@ -78,11 +78,12 @@ export function PageActions(props: PageActionProps): JSX.Element | null {
78
78
  <SplitButton
79
79
  portalled
80
80
  variant="outlined"
81
- size="medium"
81
+ size="small"
82
82
  toggleAriaLabel={translate('page.actions.moreActions', 'More actions')}
83
83
  button={
84
84
  <Button
85
85
  variant="outlined"
86
+ size="small"
86
87
  icon={renderIcon(buttonAction, actionState)}
87
88
  to={'link' in buttonAction ? buttonAction.link : undefined}
88
89
  external
@@ -112,10 +113,12 @@ function renderIcon(buttonAction: PageAction, actionState: ActionState) {
112
113
  const PageActionsWrapper = styled.div`
113
114
  margin-left: auto;
114
115
  padding-left: var(--page-actions-padding-left);
116
+ user-select: none;
115
117
  --button-color: var(--page-actions-button-text-color);
116
118
 
117
- .button-group-size-medium .button.button-size-medium {
119
+ && .button.button-size-small {
118
120
  --button-icon-left-padding: var(--page-actions-button-padding);
121
+ --button-icon-padding: var(--page-actions-toggle-padding);
119
122
  }
120
123
  `;
121
124
 
@@ -5,7 +5,8 @@ export const pageActions = css`
5
5
  --page-actions-padding-left: var(--spacing-sm);
6
6
 
7
7
  --page-actions-button-text-color: var(--text-color-secondary);
8
- --page-actions-button-padding: 5px 14px 5px var(--spacing-sm);
8
+ --page-actions-button-padding: 0 var(--spacing-sm) 0 10px;
9
+ --page-actions-toggle-padding: 4px;
9
10
 
10
11
  --page-actions-dropdown-max-height: fit-content;
11
12
 
@@ -92,7 +92,7 @@ function facetValueToSelectOption(facetValue?: SearchFacetCount | string) {
92
92
  value: facetValue,
93
93
  };
94
94
  } else {
95
- const { value, count, isCounterVisible } = facetValue as SearchFacetCount;
95
+ const { value, count, isCounterVisible } = facetValue;
96
96
  return {
97
97
  element: (
98
98
  <FilterSelectOptionWrapper>
@@ -46,8 +46,7 @@ export function Select<T>(props: SelectProps<T>): JSX.Element {
46
46
  .map((value) => {
47
47
  const selectedOption = options.find((option) => option.value === value);
48
48
  return (
49
- selectedOption ||
50
- (typeof value === 'string' ? ({ value } as SelectOption<T>) : (value as SelectOption<T>))
49
+ selectedOption || (typeof value === 'string' ? { value } : (value as SelectOption<T>))
51
50
  );
52
51
  })
53
52
  .filter((option) => !!option);
@@ -7,7 +7,7 @@ export const useThemeHooks = vi.fn(() => ({
7
7
  translate: vi.fn((key: string, defaultValue: string | { defaultValue: string }) =>
8
8
  defaultValue
9
9
  ? typeof defaultValue !== 'string'
10
- ? (defaultValue as { defaultValue: string }).defaultValue
10
+ ? defaultValue.defaultValue
11
11
  : defaultValue
12
12
  : key,
13
13
  ),
@@ -60,7 +60,15 @@ export function useActiveSectionId(
60
60
  return;
61
61
  }
62
62
 
63
+ // A one-event scroll jump is read before the section it lands on stops being skipped.
64
+ const onContentVisibilityChange = (event: Event) => {
65
+ if ('skipped' in event && event.skipped === false) {
66
+ scrollListener();
67
+ }
68
+ };
69
+
63
70
  window.addEventListener('scroll', scrollListener, { capture: false });
71
+ window.addEventListener('contentvisibilityautostatechange', onContentVisibilityChange, true);
64
72
  const initialRead = setTimeout(() => {
65
73
  scrollListener();
66
74
  }, 10);
@@ -69,6 +77,11 @@ export function useActiveSectionId(
69
77
  clearTimeout(initialRead);
70
78
  scrollListener.cancel();
71
79
  window.removeEventListener('scroll', scrollListener);
80
+ window.removeEventListener(
81
+ 'contentvisibilityautostatechange',
82
+ onContentVisibilityChange,
83
+ true,
84
+ );
72
85
  };
73
86
  }, [location, heightOffset, scrollListener]);
74
87
 
@@ -41,7 +41,7 @@ export function useCodeBlockTabsControls({ tabs, containerRef, tabRefs }: CodeBl
41
41
  resizeObserver.observe(container);
42
42
 
43
43
  return () => {
44
- resizeObserver.unobserve(container as HTMLDivElement);
44
+ resizeObserver.unobserve(container);
45
45
  };
46
46
  }
47
47
  // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -8,6 +8,7 @@ import { CopyIcon } from '@redocly/theme/icons/CopyIcon/CopyIcon';
8
8
  import { ChatGptIcon } from '@redocly/theme/icons/ChatGptIcon/ChatGptIcon';
9
9
  import { ClaudeIcon } from '@redocly/theme/icons/ClaudeIcon/ClaudeIcon';
10
10
  import { GrokIcon } from '@redocly/theme/icons/GrokIcon/GrokIcon';
11
+ import { McpIcon } from '@redocly/theme/icons/McpIcon/McpIcon';
11
12
  import { MarkdownFullIcon } from '@redocly/theme/icons/MarkdownFullIcon/MarkdownFullIcon';
12
13
  import { VSCodeIcon } from '@redocly/theme/icons/VSCodeIcon/VSCodeIcon';
13
14
  import { CursorIcon } from '@redocly/theme/icons/CursorIcon/CursorIcon';
@@ -33,9 +34,10 @@ const DEFAULT_ENABLED_ACTIONS = [
33
34
  'chatgpt',
34
35
  'claude',
35
36
  'grok',
36
- 'docs-mcp-cursor',
37
- 'docs-mcp-vscode',
38
37
  'docs-mcp-claude',
38
+ 'docs-mcp-vscode',
39
+ 'docs-mcp-cursor',
40
+ 'copy-mcp-url',
39
41
  ] as const;
40
42
 
41
43
  export type PageActionType =
@@ -44,9 +46,10 @@ export type PageActionType =
44
46
  | 'chatgpt'
45
47
  | 'claude'
46
48
  | 'grok'
47
- | 'docs-mcp-cursor'
48
- | 'docs-mcp-vscode'
49
49
  | 'docs-mcp-claude'
50
+ | 'docs-mcp-vscode'
51
+ | 'docs-mcp-cursor'
52
+ | 'copy-mcp-url'
50
53
  // Used in OpenAPI docs x-mcp
51
54
  | 'mcp-cursor'
52
55
  | 'mcp-vscode';
@@ -147,6 +150,24 @@ export function usePageActions(
147
150
  'docs-mcp-cursor': createMCPHandler('cursor', false),
148
151
  'docs-mcp-vscode': createMCPHandler('vscode', false),
149
152
  'docs-mcp-claude': createMCPHandler('claude', false),
153
+ 'copy-mcp-url': () => ({
154
+ buttonText: translate('page.actions.connectMcp.copyUrl', 'Copy MCP server URL'),
155
+ title: translate('page.actions.connectMcp.copyUrl', 'Copy MCP server URL'),
156
+ description: translate(
157
+ 'page.actions.connectMcp.copyUrlDescription',
158
+ 'Copy the MCP server URL to the clipboard',
159
+ ),
160
+ iconComponent: McpIcon,
161
+ onClick: () => {
162
+ ClipboardService.copyCustom(mcpConfig.serverUrl);
163
+ telemetry.sendPageActionsClickedMessage([
164
+ {
165
+ ...getBaseDataAttributes('pageActions', 'button'),
166
+ actionType: 'copy-mcp-url',
167
+ },
168
+ ]);
169
+ },
170
+ }),
150
171
  'mcp-cursor': createMCPHandler('cursor', true),
151
172
  'mcp-vscode': createMCPHandler('vscode', true),
152
173
 
@@ -221,6 +242,7 @@ export function usePageActions(
221
242
  isPublic,
222
243
  mcpUrl,
223
244
  mcpConfig.isMcpDisabled,
245
+ mcpConfig.serverUrl,
224
246
  pageSlug,
225
247
  actions,
226
248
  translate,
@@ -356,6 +378,7 @@ function shouldHideActionByType(
356
378
  case 'docs-mcp-cursor':
357
379
  case 'docs-mcp-vscode':
358
380
  case 'docs-mcp-claude':
381
+ case 'copy-mcp-url':
359
382
  return Boolean(mcpUrl) || isMcpDisabled;
360
383
  case 'mcp-cursor':
361
384
  case 'mcp-vscode':
@@ -16,6 +16,7 @@ import { pageActionsDarkMode } from '@redocly/theme/components/PageActions/varia
16
16
  import { tooltipDarkMode } from '@redocly/theme/components/Tooltip/variables.dark';
17
17
  import { bannerDarkMode } from '@redocly/theme/components/Banner/variables.dark';
18
18
  import { admonitionDarkMode } from '@redocly/theme/components/Admonition/variables.dark';
19
+ import { missingTranslationNoticeDarkMode } from '@redocly/theme/components/MissingTranslationNotice/variables.dark';
19
20
  import { codeBlockDarkMode } from '@redocly/theme/components/CodeBlock/variables.dark';
20
21
 
21
22
  import { activeBrandPaletteDark } from './palette.dark';
@@ -363,6 +364,7 @@ export const darkMode = css`
363
364
  ${tooltipDarkMode}
364
365
  ${bannerDarkMode}
365
366
  ${admonitionDarkMode}
367
+ ${missingTranslationNoticeDarkMode}
366
368
  ${badgesDarkMode}
367
369
  ${numberedListDarkMode}
368
370
  ${activeBrandPaletteDark}
@@ -18,6 +18,7 @@ import { dropdown } from '@redocly/theme/components/Dropdown/variables';
18
18
  import { tooltip } from '@redocly/theme/components/Tooltip/variables';
19
19
  import { checkbox } from '@redocly/theme/icons/CheckboxIcon/variables';
20
20
  import { admonition } from '@redocly/theme/components/Admonition/variables';
21
+ import { missingTranslationNotice } from '@redocly/theme/components/MissingTranslationNotice/variables';
21
22
  import { footer } from '@redocly/theme/components/Footer/variables';
22
23
  import { button } from '@redocly/theme/components/Button/variables';
23
24
  import { aiAssistantButton, connectMcpButton } from '@redocly/theme/components/Buttons/variables';
@@ -1295,6 +1296,7 @@ const replay = css`
1295
1296
  export const tokens = css`
1296
1297
  ${accordion}
1297
1298
  ${admonition}
1299
+ ${missingTranslationNotice}
1298
1300
  ${apiReferenceDocs}
1299
1301
  ${apiReferencePanels}
1300
1302
  ${badges}
@@ -39,7 +39,7 @@ import type {
39
39
  ToolCall,
40
40
  } from './search';
41
41
  import type { SubmitFeedbackParams } from './feedback';
42
- import type { TFunction } from './l10n';
42
+ import type { Locale, TFunction } from './l10n';
43
43
  import type { BreadcrumbItem } from './breadcrumb';
44
44
  import type { UseConsentResult } from './consent';
45
45
 
@@ -234,5 +234,5 @@ export type ThemeHooks = {
234
234
  export type L10nConfig = {
235
235
  currentLocale: string;
236
236
  defaultLocale: string;
237
- locales: { code: string; name: string }[];
237
+ locales: Locale[];
238
238
  };
@@ -157,11 +157,16 @@ export type TranslationKey =
157
157
  | 'page.mcpNotAllowed.snippets.chatgptApp.headline'
158
158
  | 'page.mcpNotAllowed.snippets.chatgptApp.instruction'
159
159
  | 'page.mcpNotAllowed.snippets.codexCli.headline'
160
+ | 'page.mcpNotAllowed.snippets.grokBuild.headline'
161
+ | 'page.mcpNotAllowed.snippets.grokApp.headline'
162
+ | 'page.mcpNotAllowed.snippets.grokApp.instruction'
163
+ | 'page.mcpNotAllowed.snippets.grokApp.openConnectors'
160
164
  | 'page.mcpNotAllowed.snippets.prompt.headline'
161
165
  | 'page.mcpNotAllowed.snippets.prompt.hint'
162
166
  | 'page.mcpNotAllowed.snippets.json.hint'
163
167
  | 'page.mcpNotAllowed.tabs.claude'
164
168
  | 'page.mcpNotAllowed.tabs.chatgpt'
169
+ | 'page.mcpNotAllowed.tabs.grok'
165
170
  | 'page.mcpNotAllowed.tabs.json'
166
171
  | 'page.lastUpdated.timeago'
167
172
  | 'page.lastUpdated.on'
@@ -311,6 +316,8 @@ export type TranslationKey =
311
316
  | 'page.actions.connectMcp.codexDescription'
312
317
  | 'page.actions.connectMcp.copyConfig'
313
318
  | 'page.actions.connectMcp.copyConfigDescription'
319
+ | 'page.actions.connectMcp.copyUrl'
320
+ | 'page.actions.connectMcp.copyUrlDescription'
314
321
  | 'page.actions.connectMcp.moreOptions'
315
322
  | 'page.actions.moreActions'
316
323
  | 'promptTag.copy.buttonText'
@@ -559,9 +566,12 @@ export type TranslationKey =
559
566
  | 'consent.category.analytics.description'
560
567
  | 'consent.category.marketing.title'
561
568
  | 'consent.category.marketing.description'
562
- | 'consent.settingsLink';
569
+ | 'consent.settingsLink'
570
+ | 'missingTranslationNotice.title'
571
+ | 'missingTranslationNotice.content'
572
+ | 'missingTranslationNotice.close';
563
573
 
564
- export type Locale = { code: string; name: string };
574
+ export type Locale = { code: string; name: string; showMissingTranslationNotice?: boolean };
565
575
 
566
576
  export type TFunction = (
567
577
  // A workaround for literals + strings Typescript autosuggestion: 'https://github.com/microsoft/TypeScript/issues/29729'
@@ -58,6 +58,10 @@ export function generateClaudeCodeCommand(config: McpConnectionParams): string {
58
58
  return `claude mcp add --transport http ${slugifyMcpServerName(config.serverName)} ${config.url}`;
59
59
  }
60
60
 
61
+ export function generateGrokBuildCommand(config: McpConnectionParams): string {
62
+ return `grok mcp add --transport http ${slugifyMcpServerName(config.serverName)} ${config.url}`;
63
+ }
64
+
61
65
  export function generateCodexCommand(config: McpConnectionParams): string {
62
66
  return `codex mcp add ${slugifyMcpServerName(config.serverName)} --url ${config.url}`;
63
67
  }
@@ -0,0 +1,37 @@
1
+ import React from 'react';
2
+ import styled from 'styled-components';
3
+
4
+ import type { IconProps } from '@redocly/theme/icons/types';
5
+
6
+ import { getCssColorVariable } from '@redocly/theme/core/utils';
7
+
8
+ const Icon = (props: IconProps) => (
9
+ <svg viewBox="14 13 150 154" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
10
+ <path
11
+ d="M23.5996 85.2532L86.2021 22.6507C94.8457 14.0071 108.86 14.0071 117.503 22.6507C126.147 31.2942 126.147 45.3083 117.503 53.9519L70.2254 101.23"
12
+ strokeWidth="11"
13
+ strokeLinecap="round"
14
+ />
15
+ <path
16
+ d="M70.8789 100.578L117.504 53.952C126.148 45.3083 140.163 45.3083 148.806 53.952L149.132 54.278C157.776 62.9216 157.776 76.9357 149.132 85.5792L92.5139 142.198C89.6327 145.079 89.6327 149.75 92.5139 152.631L104.14 164.257"
17
+ strokeWidth="11"
18
+ strokeLinecap="round"
19
+ />
20
+ <path
21
+ d="M101.853 38.3013L55.553 84.6011C46.9094 93.2447 46.9094 107.258 55.553 115.902C64.1966 124.546 78.2106 124.546 86.8543 115.902L133.154 69.6025"
22
+ strokeWidth="11"
23
+ strokeLinecap="round"
24
+ />
25
+ </svg>
26
+ );
27
+
28
+ export const McpIcon = styled(Icon).attrs({
29
+ 'data-component-name': 'icons/McpIcon/McpIcon',
30
+ })`
31
+ path {
32
+ stroke: ${({ color }) => getCssColorVariable(color)};
33
+ }
34
+
35
+ height: ${({ size }) => size || '16px'};
36
+ width: ${({ size }) => size || '16px'};
37
+ `;
@@ -0,0 +1,22 @@
1
+ import React from 'react';
2
+ import styled from 'styled-components';
3
+
4
+ import type { IconProps } from '@redocly/theme/icons/types';
5
+
6
+ import { getCssColorVariable } from '@redocly/theme/core/utils';
7
+
8
+ const Icon = (props: IconProps) => (
9
+ <svg viewBox="0 0 24 24" fill="inherit" xmlns="http://www.w3.org/2000/svg" {...props}>
10
+ <path d="M12.87 15.07L10.33 12.56L10.36 12.53C12.1 10.59 13.34 8.36 14.07 6H17V4H10V2H8V4H1V6H12.17C11.5 7.92 10.44 9.75 9 11.35C8.07 10.32 7.3 9.19 6.69 8H4.69C5.42 9.63 6.42 11.17 7.67 12.56L2.58 17.58L4 19L9 14L12.11 17.11L12.87 15.07ZM18.5 10H16.5L12 22H14L15.12 19H19.87L21 22H23L18.5 10ZM15.88 17L17.5 12.67L19.12 17H15.88Z" />
11
+ </svg>
12
+ );
13
+
14
+ export const TranslateIcon = styled(Icon).attrs({
15
+ 'data-component-name': 'icons/TranslateIcon/TranslateIcon',
16
+ })`
17
+ path {
18
+ fill: ${({ color }) => getCssColorVariable(color)};
19
+ }
20
+ height: ${({ size }) => size || '20px'};
21
+ width: ${({ size }) => size || '20px'};
22
+ `;
package/src/index.ts CHANGED
@@ -46,6 +46,8 @@ export * from '@redocly/theme/components/Markdown/styles/heading-anchor';
46
46
  export * from '@redocly/theme/components/Markdown/styles/links';
47
47
  /* Admonition */
48
48
  export * from '@redocly/theme/components/Admonition/Admonition';
49
+ /* MissingTranslationNotice */
50
+ export * from '@redocly/theme/components/MissingTranslationNotice/MissingTranslationNotice';
49
51
  /* ApiOnboarding */
50
52
  export * from '@redocly/theme/components/ApiOnboarding/AppCustomAttributes';
51
53
  /* Navbar */
@@ -194,6 +196,7 @@ export * from '@redocly/theme/icons/ChatGptIcon/ChatGptIcon';
194
196
  export * from '@redocly/theme/icons/CopyIcon/CopyIcon';
195
197
  export * from '@redocly/theme/icons/ClaudeIcon/ClaudeIcon';
196
198
  export * from '@redocly/theme/icons/GrokIcon/GrokIcon';
199
+ export * from '@redocly/theme/icons/McpIcon/McpIcon';
197
200
  export * from '@redocly/theme/icons/CleanIcon/CleanIcon';
198
201
  export * from '@redocly/theme/icons/GraphqlIcon/GraphqlIcon';
199
202
  export * from '@redocly/theme/icons/DeselectIcon/DeselectIcon';
@@ -5,6 +5,7 @@ import type { JSX } from 'react';
5
5
 
6
6
  import { H1_CLASS } from '@redocly/theme/core/constants';
7
7
  import { breakpoints } from '@redocly/theme/core/utils';
8
+ import { MissingTranslationNotice } from '@redocly/theme/components/MissingTranslationNotice/MissingTranslationNotice';
8
9
 
9
10
  export type CodeWalkthroughLayoutProps = React.PropsWithChildren<{
10
11
  className?: string;
@@ -16,7 +17,10 @@ export function CodeWalkthroughLayout({
16
17
  }: CodeWalkthroughLayoutProps): JSX.Element {
17
18
  return (
18
19
  <LayoutWrapper data-component-name="Layout/CodeWalkthroughLayout" className={className}>
19
- <ContentWrapper>{children}</ContentWrapper>
20
+ <ContentWrapper>
21
+ <Notice />
22
+ {children}
23
+ </ContentWrapper>
20
24
  </LayoutWrapper>
21
25
  );
22
26
  }
@@ -29,6 +33,12 @@ const LayoutWrapper = styled.div.attrs(({ className }) => ({
29
33
  width: 100%;
30
34
  `;
31
35
 
36
+ const Notice = styled(MissingTranslationNotice)`
37
+ max-width: var(--md-content-max-width);
38
+ margin-left: auto;
39
+ margin-right: auto;
40
+ `;
41
+
32
42
  const ContentWrapper = styled.section`
33
43
  width: 100%;
34
44
  --md-content-font-size: var(--font-size-lg);
@@ -10,6 +10,7 @@ import { H1_CLASS } from '@redocly/theme/core/constants';
10
10
  import { CodeSnippetProvider } from '@redocly/theme/core/contexts/CodeSnippetContext';
11
11
  import { DocumentationLayoutTop } from '@redocly/theme/layouts/DocumentationLayoutTop';
12
12
  import { DocumentationLayoutBottom } from '@redocly/theme/layouts/DocumentationLayoutBottom';
13
+ import { MissingTranslationNotice } from '@redocly/theme/components/MissingTranslationNotice/MissingTranslationNotice';
13
14
 
14
15
  type DocumentationLayoutProps = {
15
16
  tableOfContent: React.ReactNode;
@@ -48,6 +49,7 @@ export function DocumentationLayout({
48
49
  >
49
50
  <ContentWrapper>
50
51
  <DocumentationLayoutTop config={config} editPage={editPage} lastModified={lastModified} />
52
+ <MissingTranslationNotice />
51
53
  {children}
52
54
  <DocumentationLayoutBottom feedback={feedback} nextPage={nextPage} prevPage={prevPage} />
53
55
  </ContentWrapper>
@@ -5,7 +5,7 @@ import { useLocation } from 'react-router';
5
5
  import type { JSX, PropsWithChildren, ReactElement, ReactNode } from 'react';
6
6
 
7
7
  import { Image as MarkdocImage } from '@redocly/theme/markdoc/components/Image/Image';
8
- import { concatClassNames } from '@redocly/theme/core/utils';
8
+ import { breakpoints, concatClassNames } from '@redocly/theme/core/utils';
9
9
  import { LinkIcon } from '@redocly/theme/icons/LinkIcon/LinkIcon';
10
10
  import { PageActions } from '@redocly/theme/components/PageActions/PageActions';
11
11
  import { useThemeHooks } from '@redocly/theme/core/hooks';
@@ -125,6 +125,15 @@ const HeadingContentWrapper = styled.div`
125
125
  visibility: hidden;
126
126
  }
127
127
  }
128
+
129
+ @media screen and (max-width: ${breakpoints.small}) {
130
+ flex-wrap: wrap;
131
+
132
+ & > [data-component-name='PageActions/PageActions'] {
133
+ flex-basis: 100%;
134
+ --page-actions-padding-left: 0;
135
+ }
136
+ }
128
137
  `;
129
138
 
130
139
  const StyledHeadingText = styled.span`
@@ -201,7 +201,7 @@ export function collectTogglesFromChildren(
201
201
  toggles[id] = {
202
202
  when: child.attributes.when,
203
203
  unless: child.attributes.unless,
204
- } as TogglesMarkdocAttr;
204
+ };
205
205
  }
206
206
  }
207
207