@redocly/theme 0.62.0-next.4 → 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 (32) hide show
  1. package/lib/components/SvgViewer/SvgViewer.d.ts +15 -0
  2. package/lib/components/SvgViewer/SvgViewer.js +312 -0
  3. package/lib/components/SvgViewer/variables.d.ts +1 -0
  4. package/lib/components/SvgViewer/variables.dark.d.ts +1 -0
  5. package/lib/components/SvgViewer/variables.dark.js +8 -0
  6. package/lib/components/SvgViewer/variables.js +17 -0
  7. package/lib/components/Tag/variables.dark.js +6 -0
  8. package/lib/components/Tag/variables.js +6 -0
  9. package/lib/core/styles/dark.js +2 -0
  10. package/lib/core/styles/global.js +2 -0
  11. package/lib/core/types/catalog.d.ts +1 -1
  12. package/lib/core/types/l10n.d.ts +1 -1
  13. package/lib/core/utils/transform-revisions-to-version-history.js +8 -51
  14. package/lib/icons/FitToViewIcon/FitToViewIcon.d.ts +9 -0
  15. package/lib/icons/FitToViewIcon/FitToViewIcon.js +25 -0
  16. package/lib/index.d.ts +2 -0
  17. package/lib/index.js +2 -0
  18. package/lib/markdoc/components/Mermaid/Mermaid.js +70 -2
  19. package/package.json +4 -4
  20. package/src/components/SvgViewer/SvgViewer.tsx +405 -0
  21. package/src/components/SvgViewer/variables.dark.ts +5 -0
  22. package/src/components/SvgViewer/variables.ts +14 -0
  23. package/src/components/Tag/variables.dark.ts +6 -0
  24. package/src/components/Tag/variables.ts +6 -0
  25. package/src/core/styles/dark.ts +2 -0
  26. package/src/core/styles/global.ts +2 -0
  27. package/src/core/types/catalog.ts +1 -1
  28. package/src/core/types/l10n.ts +7 -1
  29. package/src/core/utils/transform-revisions-to-version-history.ts +8 -80
  30. package/src/icons/FitToViewIcon/FitToViewIcon.tsx +26 -0
  31. package/src/index.ts +2 -0
  32. package/src/markdoc/components/Mermaid/Mermaid.tsx +57 -8
@@ -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
+ `;