@apify/ui-library 0.71.1-featcolortokens-178953.58 → 0.71.1-featcolortokens-178953.67

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 (121) hide show
  1. package/dist/src/design_system/colors/generated/{dark.d.ts → css_variables.dark.d.ts} +1 -1
  2. package/dist/src/design_system/colors/generated/css_variables.dark.d.ts.map +1 -0
  3. package/dist/src/design_system/colors/generated/css_variables.dark.js +147 -0
  4. package/dist/src/design_system/colors/generated/css_variables.dark.js.map +1 -0
  5. package/dist/src/design_system/colors/generated/{light.d.ts → css_variables.light.d.ts} +1 -1
  6. package/dist/src/design_system/colors/generated/css_variables.light.d.ts.map +1 -0
  7. package/dist/src/design_system/colors/generated/css_variables.light.js +147 -0
  8. package/dist/src/design_system/colors/generated/css_variables.light.js.map +1 -0
  9. package/dist/src/design_system/colors/generated/{palette.dark.d.ts → css_variables_palette.dark.d.ts} +1 -1
  10. package/dist/src/design_system/colors/generated/css_variables_palette.dark.d.ts.map +1 -0
  11. package/dist/src/design_system/colors/generated/css_variables_palette.dark.js +74 -0
  12. package/dist/src/design_system/colors/generated/css_variables_palette.dark.js.map +1 -0
  13. package/dist/src/design_system/colors/generated/{palette.light.d.ts → css_variables_palette.light.d.ts} +1 -1
  14. package/dist/src/design_system/colors/generated/css_variables_palette.light.d.ts.map +1 -0
  15. package/dist/src/design_system/colors/generated/css_variables_palette.light.js +74 -0
  16. package/dist/src/design_system/colors/generated/css_variables_palette.light.js.map +1 -0
  17. package/dist/src/design_system/colors/index.d.ts +4 -4
  18. package/dist/src/design_system/colors/index.d.ts.map +1 -1
  19. package/dist/src/design_system/colors/index.js +4 -4
  20. package/dist/src/design_system/colors/index.js.map +1 -1
  21. package/dist/tsconfig.build.tsbuildinfo +1 -1
  22. package/package.json +3 -2
  23. package/src/codemods/generate_typograpy_tokens_files.mjs +137 -0
  24. package/src/components/action_link.tsx +60 -0
  25. package/src/components/actor_template_card.tsx +116 -0
  26. package/src/components/badge.tsx +148 -0
  27. package/src/components/banner.tsx +94 -0
  28. package/src/components/blog_article.tsx +85 -0
  29. package/src/components/box.tsx +127 -0
  30. package/src/components/button.tsx +305 -0
  31. package/src/components/chip.tsx +128 -0
  32. package/src/components/code/action_button.tsx +96 -0
  33. package/src/components/code/code_block/code_block.styled.tsx +180 -0
  34. package/src/components/code/code_block/code_block.tsx +224 -0
  35. package/src/components/code/code_block/code_block_with_tabs.tsx +257 -0
  36. package/src/components/code/code_block/utils.tsx +67 -0
  37. package/src/components/code/index.ts +5 -0
  38. package/src/components/code/inline_code/inline_code.tsx +62 -0
  39. package/src/components/code/one_line_code/one_line_code.tsx +228 -0
  40. package/src/components/code/prism_highlighter.tsx +180 -0
  41. package/src/components/color_wheel_gradient.tsx +31 -0
  42. package/src/components/floating/index.ts +3 -0
  43. package/src/components/floating/menu.tsx +189 -0
  44. package/src/components/floating/menu_common.tsx +31 -0
  45. package/src/components/floating/menu_components.tsx +99 -0
  46. package/src/components/image.tsx +24 -0
  47. package/src/components/index.ts +22 -0
  48. package/src/components/link.tsx +114 -0
  49. package/src/components/message.tsx +153 -0
  50. package/src/components/rating.tsx +106 -0
  51. package/src/components/readme_renderer/index.ts +3 -0
  52. package/src/components/readme_renderer/pythonize_value.ts +76 -0
  53. package/src/components/readme_renderer/table_of_contents.tsx +272 -0
  54. package/src/components/readme_renderer/utils.tsx +46 -0
  55. package/src/components/simple_markdown/index.ts +2 -0
  56. package/src/components/simple_markdown/simple_markdown.tsx +214 -0
  57. package/src/components/simple_markdown/simple_markdown_components.tsx +293 -0
  58. package/src/components/tabs/index.ts +2 -0
  59. package/src/components/tabs/tab.tsx +217 -0
  60. package/src/components/tabs/tabs.tsx +169 -0
  61. package/src/components/tag.tsx +196 -0
  62. package/src/components/text/heading_content.tsx +56 -0
  63. package/src/components/text/heading_marketing.tsx +55 -0
  64. package/src/components/text/heading_shared.tsx +55 -0
  65. package/src/components/text/index.ts +19 -0
  66. package/src/components/text/text_base.tsx +52 -0
  67. package/src/components/text/text_content.tsx +104 -0
  68. package/src/components/text/text_marketing.tsx +152 -0
  69. package/src/components/text/text_shared.tsx +95 -0
  70. package/src/components/tile/horizontal_tile.tsx +77 -0
  71. package/src/components/tile/index.ts +2 -0
  72. package/src/components/tile/shared.ts +27 -0
  73. package/src/components/tile/vertical_tile.tsx +59 -0
  74. package/src/components/to_consolidate/card.tsx +141 -0
  75. package/src/components/to_consolidate/index.ts +4 -0
  76. package/src/components/to_consolidate/markdown.tsx +609 -0
  77. package/src/components/to_consolidate/pagination.tsx +136 -0
  78. package/src/components/to_consolidate/tab_number_chip.tsx +31 -0
  79. package/src/design_system/colors/build_color_tokens.js +183 -0
  80. package/src/design_system/colors/figma_color_tokens.dark.json +886 -0
  81. package/src/design_system/colors/figma_color_tokens.light.json +886 -0
  82. package/src/design_system/colors/generated/colors_theme.dark.ts +110 -0
  83. package/src/design_system/colors/generated/colors_theme.light.ts +110 -0
  84. package/{dist/src/design_system/colors/generated/dark.js → src/design_system/colors/generated/css_variables.dark.ts} +1 -1
  85. package/{dist/src/design_system/colors/generated/light.js → src/design_system/colors/generated/css_variables.light.ts} +1 -1
  86. package/{dist/src/design_system/colors/generated/palette.dark.js → src/design_system/colors/generated/css_variables_palette.dark.ts} +1 -1
  87. package/{dist/src/design_system/colors/generated/palette.light.js → src/design_system/colors/generated/css_variables_palette.light.ts} +1 -1
  88. package/{dist/src/design_system/properties_theme.js → src/design_system/colors/generated/properties_theme.ts} +20 -156
  89. package/src/design_system/colors/index.ts +7 -0
  90. package/src/design_system/supernova_typography_tokens.json +657 -0
  91. package/src/design_system/theme.ts +25 -0
  92. package/src/design_system/tokens/index.ts +5 -0
  93. package/src/design_system/tokens/layouts.ts +29 -0
  94. package/src/design_system/tokens/radiuses.ts +22 -0
  95. package/src/design_system/tokens/shadows.ts +22 -0
  96. package/src/design_system/tokens/spaces.ts +15 -0
  97. package/src/design_system/tokens/transitions.ts +19 -0
  98. package/src/design_system/typography_theme.ts +197 -0
  99. package/src/index.ts +8 -0
  100. package/src/type_utils.ts +7 -0
  101. package/src/ui_dependency_provider.tsx +58 -0
  102. package/src/utils/copy_to_clipboard.ts +24 -0
  103. package/src/utils/image_color.ts +42 -0
  104. package/src/utils/index.ts +4 -0
  105. package/src/utils/resize_observer.ts +18 -0
  106. package/src/utils/sanitization.ts +14 -0
  107. package/dist/src/design_system/colors/generated/dark.d.ts.map +0 -1
  108. package/dist/src/design_system/colors/generated/dark.js.map +0 -1
  109. package/dist/src/design_system/colors/generated/light.d.ts.map +0 -1
  110. package/dist/src/design_system/colors/generated/light.js.map +0 -1
  111. package/dist/src/design_system/colors/generated/palette.dark.d.ts.map +0 -1
  112. package/dist/src/design_system/colors/generated/palette.dark.js.map +0 -1
  113. package/dist/src/design_system/colors/generated/palette.light.d.ts.map +0 -1
  114. package/dist/src/design_system/colors/generated/palette.light.js.map +0 -1
  115. package/dist/src/design_system/colors_theme.d.ts +0 -213
  116. package/dist/src/design_system/colors_theme.d.ts.map +0 -1
  117. package/dist/src/design_system/colors_theme.js +0 -213
  118. package/dist/src/design_system/colors_theme.js.map +0 -1
  119. package/dist/src/design_system/properties_theme.d.ts +0 -175
  120. package/dist/src/design_system/properties_theme.d.ts.map +0 -1
  121. package/dist/src/design_system/properties_theme.js.map +0 -1
@@ -0,0 +1,106 @@
1
+ import type { FC } from 'react';
2
+ import { Fragment, useMemo } from 'react';
3
+ import styled from 'styled-components';
4
+
5
+ import { StarEmptyIcon, StarFullIcon, StarHalfIcon } from '@apify/ui-icons';
6
+
7
+ import type { ReviewRating } from '@apify-packages/types';
8
+
9
+ import { theme } from '../design_system/theme.js';
10
+ import type { BoxProps } from './box.js';
11
+ import { Box } from './box.js';
12
+ import { Text } from './text/index.js';
13
+
14
+ type RatingStatsProps = {
15
+ ratingStats: Record<ReviewRating, number>
16
+ }
17
+
18
+ const StyledRating = styled(Box)`
19
+ display: flex;
20
+ `;
21
+
22
+ const StyledRatingStats = styled(Box)`
23
+ display: grid;
24
+ grid-template-columns: auto minmax(0, 1fr);
25
+ align-items: center;
26
+ gap: 1px ${theme.space.space8};
27
+ align-items: center;
28
+
29
+ p {
30
+ /* TODO: This font is not defined! */
31
+ line-height: 10px;
32
+ font-size: 8px;
33
+ }
34
+ `;
35
+
36
+ const StyledRatingBar = styled(Box)<{ $widthPercent: number }>`
37
+ height: 5px;
38
+ border-radius: 10px;
39
+ background: ${theme.color.neutral.overflow};
40
+ position: relative;
41
+ overflow: hidden;
42
+
43
+ &::after {
44
+ content: "";
45
+ position: absolute;
46
+ display: block;
47
+ height: 100%;
48
+ left: 0;
49
+ top: 0;
50
+ border-radius: 10px;
51
+ background: ${theme.color.neutral.icon};
52
+ width: ${({ $widthPercent }) => $widthPercent}%;
53
+
54
+ }
55
+ `;
56
+
57
+ // 0 is only for empty rating - we don't display any stars filled
58
+ type RatingProps = BoxProps & {
59
+ rating: number | undefined;
60
+ color?: string;
61
+ }
62
+
63
+ export const Rating: FC<RatingProps> = ({
64
+ rating = 0,
65
+ color = theme.color.neutral.icon,
66
+ ...rest
67
+ }) => {
68
+ const ratingStatsContent = useMemo(() => [1, 2, 3, 4, 5].map((rate) => {
69
+ const ratingFloor = Math.floor(rating);
70
+ const ratingDecimals = rating % 1;
71
+
72
+ if (ratingFloor >= rate || (ratingFloor === rate - 1 && ratingDecimals > 0.75)) return <StarFullIcon size="12" color={color} key={rate} />;
73
+ if (ratingFloor === rate - 1 && ratingDecimals > 0.25) return <StarHalfIcon size="12" color={color} key={rate} />;
74
+ return <StarEmptyIcon size="12" color={color} key={rate} />;
75
+ }), [rating, color]);
76
+
77
+ return (
78
+ <StyledRating {...rest}>
79
+ {ratingStatsContent}
80
+ </StyledRating>
81
+ );
82
+ };
83
+
84
+ export const RatingStats: FC<RatingStatsProps & BoxProps> = ({
85
+ ratingStats,
86
+ ...rest
87
+ }) => {
88
+ const totalRates = ratingStats[1] + ratingStats[2] + ratingStats[3] + ratingStats[4] + ratingStats[5];
89
+
90
+ const ratingStatsContent = useMemo(() => ([5, 4, 3, 2, 1] as const).map((rate) => {
91
+ const widthPercent = totalRates > 0 ? ((ratingStats[rate] / totalRates) * 100) : 0;
92
+
93
+ return (
94
+ <Fragment key={rate}>
95
+ <Text align='center' color={theme.color.neutral.textSubtle}>{rate}</Text>
96
+ <StyledRatingBar $widthPercent={widthPercent}/>
97
+ </Fragment>
98
+ );
99
+ }), [totalRates, ratingStats]);
100
+
101
+ return (
102
+ <StyledRatingStats {...rest}>
103
+ {ratingStatsContent}
104
+ </StyledRatingStats>
105
+ );
106
+ };
@@ -0,0 +1,3 @@
1
+ export { useActorTitleHeadingFilter } from './utils.js';
2
+ export { pythonizeValue } from './pythonize_value.js';
3
+ export { TableOfContents } from './table_of_contents.js';
@@ -0,0 +1,76 @@
1
+ /* eslint-disable prefer-template, no-control-regex */
2
+ // Escaped versions of control characters
3
+ const escapes = {
4
+ 92: '\\\\',
5
+ 34: '\\"',
6
+ 8: '\\b',
7
+ 12: '\\f',
8
+ 13: '\\r',
9
+ 9: '\\t',
10
+ };
11
+
12
+ // Replaces a control character with its escaped value
13
+ const escapeChar = (character: string) => {
14
+ const charCode = character.charCodeAt(0);
15
+ const escaped = escapes[charCode as keyof typeof escapes];
16
+ if (escaped) {
17
+ return escaped;
18
+ }
19
+ return '\\u00' + charCode.toString(16).padStart(2, '0');
20
+ };
21
+
22
+ // Escapes special characters in a string and surrounds it with proper quotes
23
+ const pythonQuoteString = (str: string, escapeNewlines = true) => {
24
+ const escapedValue = str.replace(/[\x00-\x09\x0b-\x1f\x22\x5c]/g, escapeChar);
25
+ if (!escapeNewlines && escapedValue.includes('\n')) {
26
+ return '"""' + escapedValue + '"""';
27
+ }
28
+ return '"' + escapedValue.replace(/\n/g, '\\n') + '"';
29
+ };
30
+
31
+ const INDENT = ' ';
32
+
33
+ // Converts a Javascript value to its equivalent Python representation
34
+ // Doesn't support everything, because that would be impossible,
35
+ // only the values which are supported in an input schema prefill (booleans, numbers, strings, arrays, objects)
36
+ // Tries to keep the representation short, but readable.
37
+ export const pythonizeValue = <T>(value: T, depth = 0): string | T => {
38
+ // None-like values
39
+ if (value === undefined) return 'None';
40
+ if (value === null) return 'None';
41
+
42
+ // Boolean values
43
+ if (value === true) return 'True';
44
+ if (value === false) return 'False';
45
+
46
+ // Number values (sorry, JSON doesn't support infinities)
47
+ if (value === Infinity) return 'None';
48
+ if (value === -Infinity) return 'None';
49
+ if (Number.isNaN(value)) return 'None';
50
+ if (typeof value === 'number') return value;
51
+
52
+ // String values
53
+ if (typeof value === 'string') return pythonQuoteString(value, false);
54
+
55
+ // Arrays of values
56
+ if (Array.isArray(value)) {
57
+ if (value.length === 0) return '[]';
58
+ if (value.length === 1) return '[' + pythonizeValue(value[0], depth + 1) + ']';
59
+ return '[\n'
60
+ + value.map((v) => INDENT.repeat(depth + 1) + pythonizeValue(v, depth + 1) + ',\n').join('')
61
+ + INDENT.repeat(depth) + ']';
62
+ }
63
+
64
+ // Object values
65
+ if (typeof value === 'object') {
66
+ const entries = Object.entries(value);
67
+ if (entries.length === 0) return '{}';
68
+ if (entries.length === 1) return '{ ' + pythonQuoteString(entries[0][0]) + ': ' + pythonizeValue(entries[0][1], depth + 1) + ' }';
69
+ return '{\n'
70
+ + entries.map(([k, v]) => INDENT.repeat(depth + 1) + pythonQuoteString(k) + ': ' + pythonizeValue(v, depth + 1) + ',\n').join('')
71
+ + INDENT.repeat(depth) + '}';
72
+ }
73
+
74
+ // This should never happen, if it did, something went wrong
75
+ return 'UNSUPPORTED VALUE';
76
+ };
@@ -0,0 +1,272 @@
1
+ import _ from 'lodash';
2
+ import React, {
3
+ useCallback,
4
+ useMemo,
5
+ } from 'react';
6
+ import ReactMarkdown from 'react-markdown';
7
+ import type { AllowElement } from 'react-markdown/lib/rehype-filter';
8
+ import remarkToc from 'remark-toc';
9
+ import styled from 'styled-components';
10
+
11
+ import { theme } from '../../design_system/theme.js';
12
+ import { inlineCodeStyles } from '../code/index.js';
13
+ import { Link } from '../link.js';
14
+ import { Text } from '../text/index.js';
15
+ import { cleanMarkdown, slugifyHeadingChildren } from './utils.js';
16
+
17
+ const TOC_HEADING_ID = 'Contents';
18
+
19
+ const StyledTOCLink = styled(Text)`
20
+ display: inline-block;
21
+ color: ${theme.color.neutral.textMuted};
22
+ text-decoration: none;
23
+
24
+ /* Do no change the font style if the heading is defined as bold in the markdown and is wrapped in a <strong> tag */
25
+ strong {
26
+ font-size: inherit !important;
27
+ line-height: inherit !important;
28
+ font-weight: inherit !important;
29
+ }
30
+
31
+ &:hover, &.selected {
32
+ color: ${theme.color.primary.text} !important;
33
+ }
34
+
35
+ &:before {
36
+ content: " ";
37
+ display: inline-block;
38
+ height: 100%;
39
+ left: 0;
40
+ margin-top: -1px;
41
+ position: absolute;
42
+ width: 1px;
43
+ background-color: ${theme.color.neutral.border};
44
+ }
45
+
46
+ &.selected:before {
47
+ background-color: ${theme.color.primary.text};
48
+ }
49
+ `;
50
+
51
+ const StyledTableOfContents = styled.div`
52
+ position: relative;
53
+ overflow: hidden;
54
+
55
+ p {
56
+ margin: 0;
57
+ }
58
+
59
+ .level-3 a {
60
+ color: ${theme.color.neutral.textSubtle};
61
+ }
62
+
63
+ ul {
64
+ padding-left: ${theme.space.space8} !important;
65
+ list-style: none;
66
+ margin: 0;
67
+ overflow-y: auto;
68
+
69
+ &.level-3 {
70
+ padding-left: ${theme.space.space16} !important;
71
+ max-height: 0;
72
+ overflow: hidden;
73
+ transition: all .3s ease-in-out;
74
+
75
+ &.expanded {
76
+ max-height: 1000px;
77
+ }
78
+ }
79
+ }
80
+
81
+ .inline-code {
82
+ ${inlineCodeStyles}
83
+ }
84
+ `;
85
+
86
+ interface CustomHTMLAnchorElement extends Omit<HTMLAnchorElement, 'children' | 'parentNode'> {
87
+ hash: string;
88
+ tagName: string;
89
+ type: string;
90
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
91
+ parentNode: any;
92
+ }
93
+
94
+ const handleScroll = (anchors: CustomHTMLAnchorElement[], headlines: HTMLElement[], anchorLists: HTMLUListElement[] | null, headingOffsetPx: number) => {
95
+ if (!anchors || !headlines) return;
96
+
97
+ // Items that are past scroll
98
+ const headlinesPastScrollPosition = headlines.filter((headline) => {
99
+ return headline.getBoundingClientRect().top - headingOffsetPx < 0;
100
+ });
101
+
102
+ // Last item that is past the scroll is current in view. If no headline is past the scroll, let's select the first one
103
+ const currentHeadlineId = headlinesPastScrollPosition[headlinesPastScrollPosition.length - 1]?.id || headlines[0]?.id;
104
+
105
+ // Highlighting anchor of current headline
106
+ if (currentHeadlineId) {
107
+ const currentHash = `#${currentHeadlineId}`;
108
+
109
+ anchorLists?.forEach((anchorList) => anchorList.classList.remove('expanded'));
110
+
111
+ // Remove selected class from all anchors and only add it to the current one
112
+ anchors.forEach((anchor) => {
113
+ anchor.classList.remove('selected');
114
+ if (anchor.hash === currentHash) {
115
+ anchor.classList.add('selected');
116
+
117
+ const grandparent = anchor.parentNode?.parentNode;
118
+
119
+ // Structure of the table of content is normalized so we can figure out if section should be expanded
120
+ // by tag and className of neighboring nodes. There are two cases:
121
+
122
+ // 1) If h2 is selected, we want to expand its section with h3 headings
123
+ if (grandparent?.tagName === 'LI' && grandparent.children[1]?.tagName === 'UL' && grandparent.children[1]?.classList.contains('level-3')) {
124
+ grandparent.children[1].classList.add('expanded');
125
+ }
126
+
127
+ // 2) We also want to expand section of h3 headings that follow h1 right away
128
+ if (grandparent?.tagName === 'UL' && grandparent.classList.contains('level-3')) {
129
+ grandparent.classList.add('expanded');
130
+ }
131
+ }
132
+ });
133
+ }
134
+ };
135
+
136
+ const assignHeadingLevelsRecursively = (
137
+ {
138
+ children,
139
+ tagName,
140
+ properties,
141
+ }: {
142
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
143
+ children: any[],
144
+ tagName: string,
145
+ properties: { className: string }
146
+ },
147
+ level: number,
148
+ ) => {
149
+ if (level > 3) return;
150
+ let currentLevel = level;
151
+ if (tagName === 'ul') {
152
+ // eslint-disable-next-line no-param-reassign
153
+ properties.className = `level-${currentLevel}`; // assign level className
154
+ currentLevel += 1; // increase current heading level;
155
+ }
156
+
157
+ children?.forEach((child) => {
158
+ // We only care about ul and li elements - we don't need to traverse any other
159
+ if (child.type === 'element' && (tagName === 'ul' || tagName === 'li')) {
160
+ assignHeadingLevelsRecursively(child, currentLevel);
161
+ }
162
+ });
163
+ };
164
+
165
+ export interface TableOfContentsProps {
166
+ markdown: string;
167
+ headingOffsetPx?: number; // How far from the top should the toc detect the current heading
168
+ // Function where we can define which elements are allowed in the markdown. See https://github.com/remarkjs/react-markdown#props for more info
169
+ allowElement?: AllowElement;
170
+ }
171
+
172
+ const TableOfContentsComponent = ({
173
+ markdown,
174
+ headingOffsetPx = 10,
175
+ allowElement,
176
+ ...rest
177
+ }: TableOfContentsProps) => {
178
+ const cleanedMarkdown = useMemo(() => {
179
+ const cleanedMarkdownString = cleanMarkdown(markdown);
180
+ return `### ${TOC_HEADING_ID}\n${cleanedMarkdownString}`;
181
+ }, [markdown]);
182
+
183
+ const hasTopLevelHeading = useMemo(() => !!cleanedMarkdown.match(/^#\s.+$/m), [cleanedMarkdown]);
184
+
185
+ const tocRef = useCallback((node: HTMLElement | null) => {
186
+ const anchors = node ? Array.from(node.querySelectorAll('a')) : [];
187
+ const anchorLists = node ? Array.from(node.querySelectorAll('ul')) : null;
188
+ const headlines = (anchors)
189
+ .map((anchor) => document.getElementById(anchor.hash.substr(1)))
190
+ .filter((headline) => headline !== undefined && headline !== null) as HTMLElement[];
191
+
192
+ const scrollHandler = () => handleScroll(anchors, headlines, anchorLists, headingOffsetPx);
193
+
194
+ // Callback refs cannot return cleanup functions - but they are called again with null on unmount
195
+ if (node) {
196
+ window.addEventListener('scroll', scrollHandler);
197
+ } else {
198
+ window.removeEventListener('scroll', scrollHandler);
199
+ }
200
+ scrollHandler(); // call for the first time so we select the first heading without scrolling
201
+ }, [headingOffsetPx]);
202
+
203
+ return (
204
+ <StyledTableOfContents ref={tocRef} {...rest}>
205
+ <ReactMarkdown
206
+ allowElement={allowElement}
207
+ remarkPlugins={[
208
+ [remarkToc, { heading: TOC_HEADING_ID, maxDepth: 3 }],
209
+ () => ({ children, ...nodeRest }) => {
210
+ // TOC plug-ins only inject content table to existing markdown documents - only generating it is not a common use-case
211
+ // To make is happen, we can just take the generated node where toc is located and throw away the rest.
212
+ // children[0] is the heading where toc is placed (that's plug-in's requirement)
213
+ // children[1] is the generated table of contents we can simply pick
214
+
215
+ let tocContentNode = children[1];
216
+
217
+ // If there is no H1 heading in the readme then assignHeadingLevelsRecursively does not work correctly.
218
+ // We need to 'normalize' the final node to have all the levels we expect
219
+ // - solution is to wrap the content in extra node that mimic this H1 group that remarkToc would normally create
220
+ if (!hasTopLevelHeading) {
221
+ tocContentNode = {
222
+ type: 'list',
223
+ ordered: false,
224
+ spread: false,
225
+ children: [
226
+ {
227
+ type: 'listItem',
228
+ spread: true,
229
+ children: [
230
+ tocContentNode,
231
+ ],
232
+ },
233
+ ],
234
+ };
235
+ }
236
+
237
+ return {
238
+ ...nodeRest,
239
+ children: [
240
+ tocContentNode,
241
+ ],
242
+ };
243
+ },
244
+ ]}
245
+ rehypePlugins={[() => (input) => {
246
+ // this plug-in already works with html node representation so we can assign classNames that we need
247
+ // in order to allow hiding blocks with h3 headings that are out of the viewport
248
+ assignHeadingLevelsRecursively(input.children[0], 1);
249
+ return input;
250
+ }]}
251
+ components={{
252
+ a: ({ children }) => (
253
+ <StyledTOCLink
254
+ forwardedAs={Link}
255
+ to={`#${slugifyHeadingChildren(children)}`}
256
+ py={'space4'}
257
+ >
258
+ {children}
259
+ </StyledTOCLink>
260
+ ),
261
+ code: ({ children }) => (
262
+ <code className="inline-code">{children}</code>
263
+ ),
264
+ }}
265
+ >
266
+ {cleanedMarkdown}
267
+ </ReactMarkdown>
268
+ </StyledTableOfContents>
269
+ );
270
+ };
271
+
272
+ export const TableOfContents = React.memo(TableOfContentsComponent, (prevProps, nextProps) => _.isEqual(prevProps, nextProps));
@@ -0,0 +1,46 @@
1
+ import React, { useCallback } from 'react';
2
+ import type { AllowElement } from 'react-markdown/lib/rehype-filter';
3
+ import slugify from 'slugify';
4
+
5
+ export const slugifyHeadingChildren = (
6
+ headingChildren: React.ReactNode,
7
+ ): string | undefined => {
8
+ if (!headingChildren) return undefined;
9
+
10
+ const slugs: string[] = [];
11
+ React.Children.forEach(headingChildren, (child) => {
12
+ if (typeof child === 'string') {
13
+ slugs.push(slugify(child, { lower: true, strict: true }));
14
+ } else if (React.isValidElement(child) && child.props.children) {
15
+ const nestedSlugs = slugifyHeadingChildren(child.props.children);
16
+ if (nestedSlugs) slugs.push(nestedSlugs);
17
+ }
18
+ });
19
+
20
+ return slugs.join('-');
21
+ };
22
+
23
+ export const cleanMarkdown = (markdown: string, removeFirstH1?: boolean): string => {
24
+ // Remove the table of contents as we are generating our own
25
+ let clean = markdown
26
+ .replace(/<!-- toc start -->.*?<!-- toc end -->/s, '')
27
+ .replace(/##.*content.*?\n*<!-- toc -->(.|[\r\n])*<!-- tocstop -->\n*/, '');
28
+
29
+ // Remove first h1 if removeFirstH1 is true
30
+ if (removeFirstH1) clean = clean.replace(removeFirstH1 ? /^#\s.+$/m : '', '');
31
+
32
+ return clean.trim();
33
+ };
34
+
35
+ // This removes the first element if it's a `h1` containing exactly the Actor title
36
+ export const useActorTitleHeadingFilter = (actorTitle: string): AllowElement => {
37
+ return useCallback((element, index, parent) => {
38
+ if (parent.type === 'root'
39
+ && index === 0
40
+ && element.tagName === 'h1'
41
+ && element.children.length === 1
42
+ && element.children[0].type === 'text'
43
+ && element.children[0].value?.toLowerCase() === actorTitle.toLowerCase()) return false;
44
+ return true;
45
+ }, [actorTitle]);
46
+ };
@@ -0,0 +1,2 @@
1
+ export * from './simple_markdown.js';
2
+ export * from './simple_markdown_components.js';