@redocly/theme 0.62.0-next.3 → 0.62.0-next.5

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 (34) hide show
  1. package/lib/components/Search/SearchDialog.js +11 -2
  2. package/lib/components/SvgViewer/SvgViewer.d.ts +15 -0
  3. package/lib/components/SvgViewer/SvgViewer.js +312 -0
  4. package/lib/components/SvgViewer/variables.d.ts +1 -0
  5. package/lib/components/SvgViewer/variables.dark.d.ts +1 -0
  6. package/lib/components/SvgViewer/variables.dark.js +8 -0
  7. package/lib/components/SvgViewer/variables.js +17 -0
  8. package/lib/components/Tag/variables.dark.js +6 -0
  9. package/lib/components/Tag/variables.js +6 -0
  10. package/lib/core/styles/dark.js +2 -0
  11. package/lib/core/styles/global.js +2 -0
  12. package/lib/core/types/catalog.d.ts +1 -1
  13. package/lib/core/types/l10n.d.ts +1 -1
  14. package/lib/core/utils/transform-revisions-to-version-history.js +8 -51
  15. package/lib/icons/FitToViewIcon/FitToViewIcon.d.ts +9 -0
  16. package/lib/icons/FitToViewIcon/FitToViewIcon.js +25 -0
  17. package/lib/index.d.ts +2 -0
  18. package/lib/index.js +2 -0
  19. package/lib/markdoc/components/Mermaid/Mermaid.js +70 -2
  20. package/package.json +4 -4
  21. package/src/components/Search/SearchDialog.tsx +29 -14
  22. package/src/components/SvgViewer/SvgViewer.tsx +405 -0
  23. package/src/components/SvgViewer/variables.dark.ts +5 -0
  24. package/src/components/SvgViewer/variables.ts +14 -0
  25. package/src/components/Tag/variables.dark.ts +6 -0
  26. package/src/components/Tag/variables.ts +6 -0
  27. package/src/core/styles/dark.ts +2 -0
  28. package/src/core/styles/global.ts +2 -0
  29. package/src/core/types/catalog.ts +1 -1
  30. package/src/core/types/l10n.ts +8 -1
  31. package/src/core/utils/transform-revisions-to-version-history.ts +8 -80
  32. package/src/icons/FitToViewIcon/FitToViewIcon.tsx +26 -0
  33. package/src/index.ts +2 -0
  34. package/src/markdoc/components/Mermaid/Mermaid.tsx +57 -8
@@ -295,6 +295,12 @@ export const tag = css`
295
295
  --tag-bg-color-hover: #CEDDFD; // @presenter Color
296
296
  }
297
297
 
298
+ .tag-query {
299
+ --tag-color: #25b869; // @presenter Color
300
+ --tag-bg-color: #e5faef; // @presenter Color
301
+ --tag-bg-color-hover: #D4F7E5; // @presenter Color
302
+ }
303
+
298
304
  .tag-put {
299
305
  --tag-color: #f5901d; // @presenter Color
300
306
  --tag-bg-color: #fef1e2; // @presenter Color
@@ -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 { svgViewerDarkMode } from '@redocly/theme/components/SvgViewer/variables.dark';
19
20
 
20
21
  const replayDarkMode = css`
21
22
  /**
@@ -332,6 +333,7 @@ export const darkMode = css`
332
333
  ${tooltipDarkMode}
333
334
  ${bannerDarkMode}
334
335
  ${admonitionDarkMode}
336
+ ${svgViewerDarkMode}
335
337
 
336
338
  /**
337
339
  * @tokens Dark Theme Scrollbar Config
@@ -42,6 +42,7 @@ import { cards } from '@redocly/theme/markdoc/components/Cards/variables';
42
42
  import { codeWalkthrough } from '@redocly/theme/markdoc/components/CodeWalkthrough/variables';
43
43
  import { skipContent } from '@redocly/theme/components/SkipContent/variables';
44
44
  import { pageActions } from '@redocly/theme/components/PageActions/variables';
45
+ import { svgViewer } from '@redocly/theme/components/SvgViewer/variables';
45
46
 
46
47
  import { darkMode } from './dark';
47
48
 
@@ -1319,6 +1320,7 @@ export const styles = css`
1319
1320
  ${replay}
1320
1321
  ${skipContent}
1321
1322
  ${pageActions}
1323
+ ${svgViewer}
1322
1324
 
1323
1325
  background-color: var(--bg-color);
1324
1326
  color: var(--text-color-primary);
@@ -154,7 +154,7 @@ export type BffCatalogRelatedEntity = {
154
154
  key: string;
155
155
  title: string;
156
156
  summary?: string | null;
157
- readonly source: 'api' | 'file';
157
+ readonly source: 'remote' | 'file';
158
158
  relationRole: 'source' | 'target';
159
159
  relationType: EntityRelationType;
160
160
  sourceFile?: string | null;
@@ -87,6 +87,7 @@ export type TranslationKey =
87
87
  | 'search.ai.newConversation'
88
88
  | 'search.ai.backToSearch'
89
89
  | 'search.ai.back'
90
+ | 'search.ai.assistant'
90
91
  | 'search.ai.placeholder'
91
92
  | 'search.ai.generatingResponse'
92
93
  | 'search.ai.followUpQuestion'
@@ -413,7 +414,13 @@ export type TranslationKey =
413
414
  | 'select.noResults'
414
415
  | 'loaders.loading'
415
416
  | 'filter.dateRange.from'
416
- | 'filter.dateRange.to';
417
+ | 'filter.dateRange.to'
418
+ | 'mermaid.openFullscreen'
419
+ | 'mermaid.zoomIn'
420
+ | 'mermaid.zoomOut'
421
+ | 'mermaid.reset'
422
+ | 'mermaid.close'
423
+ | 'mermaid.viewer';
417
424
 
418
425
  export type Locale = { code: string; name: string };
419
426
 
@@ -8,72 +8,6 @@ import { VERSION_NOT_SPECIFIED } from '@redocly/theme/core/constants';
8
8
 
9
9
  import { toLocalizedShortDate, toLocalizedShortDateTime } from './date';
10
10
 
11
- function compareVersionsDescending(versionA: string, versionB: string): number {
12
- return versionB.localeCompare(versionA, undefined, { numeric: true });
13
- }
14
-
15
- function extractGroupComparisonData(group: CatalogEntityVersionHistoryGroup): {
16
- version: string;
17
- isNotSpecified: boolean;
18
- time: number;
19
- hasCurrent: boolean;
20
- } {
21
- const version = group.version;
22
- const isNotSpecified = version === VERSION_NOT_SPECIFIED;
23
-
24
- // Get version dates (oldest revision date for each group - the version's creation date)
25
- // This ensures versions stay in their original order even when new revisions are added
26
- const date =
27
- group.singleRevisionDate ||
28
- (group.revisions && group.revisions.length > 0
29
- ? group.revisions[group.revisions.length - 1]?.revisionDate
30
- : undefined) ||
31
- '';
32
- const time = date ? new Date(date).getTime() : 0;
33
-
34
- const hasCurrent = group.hasCurrentRevisionFromBackend ?? false;
35
-
36
- return { version, isNotSpecified, time, hasCurrent };
37
- }
38
-
39
- function compareVersionGroupsDescending(
40
- groupA: CatalogEntityVersionHistoryGroup,
41
- groupB: CatalogEntityVersionHistoryGroup,
42
- ): number {
43
- const {
44
- version: versionA,
45
- isNotSpecified: isNotSpecifiedA,
46
- time: timeA,
47
- hasCurrent: hasCurrentA,
48
- } = extractGroupComparisonData(groupA);
49
- const {
50
- version: versionB,
51
- isNotSpecified: isNotSpecifiedB,
52
- time: timeB,
53
- hasCurrent: hasCurrentB,
54
- } = extractGroupComparisonData(groupB);
55
-
56
- // First, compare by version date (oldest first, so most recent versions come first)
57
- const dateComparison = timeB - timeA;
58
- if (dateComparison !== 0) {
59
- return dateComparison;
60
- }
61
-
62
- // If dates are equal, prioritize the one with isCurrent from backend
63
- if (hasCurrentA !== hasCurrentB) {
64
- return hasCurrentB ? 1 : -1; // hasCurrentB comes first if true
65
- }
66
-
67
- // If both have same isCurrent status, compare by version
68
- // If both are unknown or both are real versions, compare by version
69
- if (isNotSpecifiedA === isNotSpecifiedB) {
70
- return compareVersionsDescending(versionA, versionB);
71
- }
72
-
73
- // If one is unknown and one is a real version, unknown comes last
74
- return isNotSpecifiedA ? 1 : -1;
75
- }
76
-
77
11
  export type TransformRevisionsToVersionHistoryParams = {
78
12
  revisions: BffCatalogEntityRevision[];
79
13
  currentRevisionDate?: string;
@@ -100,13 +34,7 @@ export function transformRevisionsToVersionHistory({
100
34
  });
101
35
 
102
36
  const versionGroups = Array.from(versionMap.entries()).map(([version, versionRevisions]) => {
103
- const sortedRevisions = [...versionRevisions].sort((a, b) => {
104
- const dateA = a.revision ? new Date(a.revision).getTime() : 0;
105
- const dateB = b.revision ? new Date(b.revision).getTime() : 0;
106
- return dateB - dateA;
107
- });
108
-
109
- const latestRevision = sortedRevisions[0];
37
+ const latestRevision = versionRevisions[0];
110
38
 
111
39
  const versionMatches =
112
40
  normalizedCurrentVersion === undefined || normalizedCurrentVersion === version;
@@ -114,7 +42,7 @@ export function transformRevisionsToVersionHistory({
114
42
  let isCurrent: boolean;
115
43
  if (currentRevisionDate !== undefined) {
116
44
  // Check if revision matches AND version matches
117
- const revisionMatches = sortedRevisions.some((rev) => rev.revision === currentRevisionDate);
45
+ const revisionMatches = versionRevisions.some((rev) => rev.revision === currentRevisionDate);
118
46
  isCurrent = revisionMatches && versionMatches;
119
47
  } else {
120
48
  // When no revision is specified, use isCurrent flag from the latest revision
@@ -123,11 +51,11 @@ export function transformRevisionsToVersionHistory({
123
51
  }
124
52
 
125
53
  // Check if any revision in this version group is the default version
126
- const isDefaultVersion = sortedRevisions.some((rev) => rev.isDefaultVersion === true);
54
+ const isDefaultVersion = versionRevisions.some((rev) => rev.isDefaultVersion === true);
127
55
 
128
56
  const revisions =
129
- sortedRevisions.length > 1
130
- ? sortedRevisions.map((rev, index): CatalogEntityRevision => {
57
+ versionRevisions.length > 1
58
+ ? versionRevisions.map((rev, index): CatalogEntityRevision => {
131
59
  const revisionMatches = currentRevisionDate
132
60
  ? rev.revision === currentRevisionDate
133
61
  : false;
@@ -138,7 +66,7 @@ export function transformRevisionsToVersionHistory({
138
66
  index === 0 &&
139
67
  isCurrent;
140
68
  return {
141
- name: `r.${sortedRevisions.length - index}`,
69
+ name: `r.${versionRevisions.length - index}`,
142
70
  date: toLocalizedShortDateTime(rev.revision, locale),
143
71
  revisionDate: rev.revision,
144
72
  isActive: isActiveRevision || isCurrentByDefault,
@@ -155,9 +83,9 @@ export function transformRevisionsToVersionHistory({
155
83
  hasCurrentRevisionFromBackend: latestRevision?.isCurrent ?? false,
156
84
  isDefaultVersion,
157
85
  revisions,
158
- singleRevisionDate: sortedRevisions.length === 1 ? sortedRevisions[0]?.revision : undefined,
86
+ singleRevisionDate: versionRevisions.length === 1 ? versionRevisions[0]?.revision : undefined,
159
87
  };
160
88
  });
161
89
 
162
- return versionGroups.sort(compareVersionGroupsDescending);
90
+ return versionGroups;
163
91
  }
@@ -0,0 +1,26 @@
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 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
10
+ <path d="M1 1H5V2.5H2.5V5H1V1Z" />
11
+ <path d="M15 1H11V2.5H13.5V5H15V1Z" />
12
+ <path d="M1 15H5V13.5H2.5V11H1V15Z" />
13
+ <path d="M15 15H11V13.5H13.5V11H15V15Z" />
14
+ </svg>
15
+ );
16
+
17
+ export const FitToViewIcon = styled(Icon).attrs(() => ({
18
+ 'data-component-name': 'icons/FitToViewIcon/FitToViewIcon',
19
+ }))<IconProps>`
20
+ path {
21
+ fill: ${({ color }) => getCssColorVariable(color)};
22
+ }
23
+
24
+ height: ${({ size }) => size || '16px'};
25
+ width: ${({ size }) => size || '16px'};
26
+ `;
package/src/index.ts CHANGED
@@ -27,6 +27,7 @@ export * from '@redocly/theme/components/Tags/CounterTag';
27
27
  export * from '@redocly/theme/components/VersionPicker/VersionPicker';
28
28
  export * from '@redocly/theme/components/Marker/Marker';
29
29
  export * from '@redocly/theme/components/PageActions/PageActions';
30
+ export * from '@redocly/theme/components/SvgViewer/SvgViewer';
30
31
  /* Buttons */
31
32
  export * from '@redocly/theme/components/Buttons/CopyButton';
32
33
  export * from '@redocly/theme/components/Buttons/EditPageButton';
@@ -293,6 +294,7 @@ export * from '@redocly/theme/icons/WorkflowHierarchyIcon/WorkflowHierarchyIcon'
293
294
  export * from '@redocly/theme/icons/GenericIcon/GenericIcon';
294
295
  export * from '@redocly/theme/icons/ShareIcon/ShareIcon';
295
296
  export * from '@redocly/theme/icons/HashtagIcon/HashtagIcon';
297
+ export * from '@redocly/theme/icons/FitToViewIcon/FitToViewIcon';
296
298
  /* Layouts */
297
299
  export * from '@redocly/theme/layouts/RootLayout';
298
300
  export * from '@redocly/theme/layouts/PageLayout';
@@ -1,9 +1,11 @@
1
- import React from 'react';
1
+ import React, { useState } from 'react';
2
2
  import styled from 'styled-components';
3
3
 
4
4
  import type { JSX } from 'react';
5
5
 
6
6
  import { concatClassNames } from '@redocly/theme/core/utils';
7
+ import { useThemeHooks } from '@redocly/theme/core/hooks';
8
+ import { SvgViewer } from '@redocly/theme/components/SvgViewer/SvgViewer';
7
9
 
8
10
  type MermaidProps = {
9
11
  diagramHtml: string;
@@ -18,20 +20,55 @@ export function Mermaid({
18
20
  'data-hash': dataHash,
19
21
  className,
20
22
  }: MermaidProps): JSX.Element {
23
+ const { useTranslate } = useThemeHooks();
24
+ const { translate } = useTranslate();
25
+ const [isOpen, setIsOpen] = useState(false);
26
+
27
+ const open = () => setIsOpen(true);
28
+ const close = () => setIsOpen(false);
29
+
21
30
  return (
22
- <Wrapper
23
- className={concatClassNames('mermaid-wrapper', className)}
24
- dangerouslySetInnerHTML={{ __html: diagramHtml }}
25
- data-component-name="Markdoc/Mermaid/Mermaid"
26
- data-source={dataSource}
27
- data-hash={dataHash}
28
- />
31
+ <>
32
+ <Wrapper
33
+ className={concatClassNames('mermaid-wrapper', className)}
34
+ dangerouslySetInnerHTML={{ __html: diagramHtml }}
35
+ data-component-name="Markdoc/Mermaid/Mermaid"
36
+ data-source={dataSource}
37
+ data-hash={dataHash}
38
+ onClick={open}
39
+ onKeyDown={(e) => e.key === 'Enter' || (e.key === ' ' && open())}
40
+ role="button"
41
+ tabIndex={0}
42
+ aria-label={translate('mermaid.openFullscreen', 'Click to open diagram in fullscreen')}
43
+ />
44
+ <SvgViewer
45
+ isOpen={isOpen}
46
+ onClose={close}
47
+ labels={{
48
+ zoomIn: translate('mermaid.zoomIn', 'Zoom in'),
49
+ zoomOut: translate('mermaid.zoomOut', 'Zoom out'),
50
+ fitToView: translate('mermaid.reset', 'Fit to view'),
51
+ close: translate('mermaid.close', 'Close'),
52
+ dialogLabel: translate('mermaid.viewer', 'Mermaid diagram viewer'),
53
+ }}
54
+ >
55
+ <ViewerContent dangerouslySetInnerHTML={{ __html: diagramHtml }} />
56
+ </SvgViewer>
57
+ </>
29
58
  );
30
59
  }
31
60
 
32
61
  const Wrapper = styled.div`
33
62
  background-color: var(--mermaid-bg-color);
34
63
  border-radius: var(--mermaid-border-radius);
64
+ cursor: pointer;
65
+ transition: box-shadow 0.2s ease;
66
+
67
+ &:hover,
68
+ &:focus {
69
+ outline: none;
70
+ box-shadow: 0 0 0 2px var(--border-color-input-focus);
71
+ }
35
72
 
36
73
  * {
37
74
  font-family: var(--mermaid-font-family) !important;
@@ -42,3 +79,15 @@ const Wrapper = styled.div`
42
79
  max-width: 100%;
43
80
  }
44
81
  `;
82
+
83
+ const ViewerContent = styled.div`
84
+ * {
85
+ font-family: var(--mermaid-font-family) !important;
86
+ }
87
+
88
+ .mermaid > svg {
89
+ font-size: var(--font-size-base) !important;
90
+ display: block;
91
+ max-width: none !important;
92
+ }
93
+ `;