@san-siva/blogkit 1.0.12 → 1.1.0

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 (196) hide show
  1. package/README.md +64 -13
  2. package/dist/cjs/components/Blog.js +3 -118
  3. package/dist/cjs/components/Blog.js.map +1 -1
  4. package/dist/cjs/components/BlogHeader.js +4 -4
  5. package/dist/cjs/components/BlogHeader.js.map +1 -1
  6. package/dist/cjs/components/BlogLink.js +4 -23
  7. package/dist/cjs/components/BlogLink.js.map +1 -1
  8. package/dist/cjs/components/BlogSection.js +4 -29
  9. package/dist/cjs/components/BlogSection.js.map +1 -1
  10. package/dist/cjs/components/Callout.js +3 -4
  11. package/dist/cjs/components/Callout.js.map +1 -1
  12. package/dist/cjs/components/CodeBlock.js +3 -25
  13. package/dist/cjs/components/CodeBlock.js.map +1 -1
  14. package/dist/cjs/components/Mermaid.js +3 -46
  15. package/dist/cjs/components/Mermaid.js.map +1 -1
  16. package/dist/cjs/components/Table.js +2 -7
  17. package/dist/cjs/components/Table.js.map +1 -1
  18. package/dist/cjs/dynamicComponents/BlogDynamic.js +167 -0
  19. package/dist/cjs/dynamicComponents/BlogDynamic.js.map +1 -0
  20. package/dist/cjs/dynamicComponents/BlogHeaderDynamic.js +13 -0
  21. package/dist/cjs/dynamicComponents/BlogHeaderDynamic.js.map +1 -0
  22. package/dist/cjs/dynamicComponents/BlogLinkDynamic.js +35 -0
  23. package/dist/cjs/dynamicComponents/BlogLinkDynamic.js.map +1 -0
  24. package/dist/cjs/dynamicComponents/BlogSectionDynamic.js +50 -0
  25. package/dist/cjs/dynamicComponents/BlogSectionDynamic.js.map +1 -0
  26. package/dist/cjs/dynamicComponents/CalloutDynamic.js +14 -0
  27. package/dist/cjs/dynamicComponents/CalloutDynamic.js.map +1 -0
  28. package/dist/cjs/dynamicComponents/CodeBlockDynamic.js +38 -0
  29. package/dist/cjs/dynamicComponents/CodeBlockDynamic.js.map +1 -0
  30. package/dist/cjs/dynamicComponents/MermaidDynamic.js +59 -0
  31. package/dist/cjs/dynamicComponents/MermaidDynamic.js.map +1 -0
  32. package/dist/cjs/dynamicComponents/TableDynamic.js +18 -0
  33. package/dist/cjs/dynamicComponents/TableDynamic.js.map +1 -0
  34. package/dist/cjs/index.css +2 -0
  35. package/dist/cjs/index.css.map +1 -0
  36. package/dist/cjs/staticComponents/BlogLinkStatic.js +17 -0
  37. package/dist/cjs/staticComponents/BlogLinkStatic.js.map +1 -0
  38. package/dist/cjs/staticComponents/BlogSectionStatic.js +19 -0
  39. package/dist/cjs/staticComponents/BlogSectionStatic.js.map +1 -0
  40. package/dist/cjs/staticComponents/BlogStatic.js +13 -0
  41. package/dist/cjs/staticComponents/BlogStatic.js.map +1 -0
  42. package/dist/cjs/staticComponents/CodeBlockStatic.js +13 -0
  43. package/dist/cjs/staticComponents/CodeBlockStatic.js.map +1 -0
  44. package/dist/cjs/staticComponents/MermaidStatic.js +13 -0
  45. package/dist/cjs/staticComponents/MermaidStatic.js.map +1 -0
  46. package/dist/cjs/styles/Blog.module.scss.js +0 -4
  47. package/dist/cjs/styles/Blog.module.scss.js.map +1 -1
  48. package/dist/cjs/styles/BlogHeader.module.scss.js +0 -4
  49. package/dist/cjs/styles/BlogHeader.module.scss.js.map +1 -1
  50. package/dist/cjs/styles/BlogLink.module.scss.js +0 -4
  51. package/dist/cjs/styles/BlogLink.module.scss.js.map +1 -1
  52. package/dist/cjs/styles/BlogSection.module.scss.js +0 -4
  53. package/dist/cjs/styles/BlogSection.module.scss.js.map +1 -1
  54. package/dist/cjs/styles/Callout.module.scss.js +0 -4
  55. package/dist/cjs/styles/Callout.module.scss.js.map +1 -1
  56. package/dist/cjs/styles/CodeBlock.module.scss.js +1 -5
  57. package/dist/cjs/styles/CodeBlock.module.scss.js.map +1 -1
  58. package/dist/cjs/styles/Mermaid.module.scss.js +0 -4
  59. package/dist/cjs/styles/Mermaid.module.scss.js.map +1 -1
  60. package/dist/cjs/styles/Table.module.scss.js +0 -4
  61. package/dist/cjs/styles/Table.module.scss.js.map +1 -1
  62. package/dist/esm/components/Blog.js +5 -120
  63. package/dist/esm/components/Blog.js.map +1 -1
  64. package/dist/esm/components/BlogHeader.js +5 -5
  65. package/dist/esm/components/BlogHeader.js.map +1 -1
  66. package/dist/esm/components/BlogLink.js +6 -25
  67. package/dist/esm/components/BlogLink.js.map +1 -1
  68. package/dist/esm/components/BlogSection.js +6 -31
  69. package/dist/esm/components/BlogSection.js.map +1 -1
  70. package/dist/esm/components/Callout.js +4 -5
  71. package/dist/esm/components/Callout.js.map +1 -1
  72. package/dist/esm/components/CodeBlock.js +5 -27
  73. package/dist/esm/components/CodeBlock.js.map +1 -1
  74. package/dist/esm/components/Mermaid.js +5 -48
  75. package/dist/esm/components/Mermaid.js.map +1 -1
  76. package/dist/esm/components/Table.js +3 -8
  77. package/dist/esm/components/Table.js.map +1 -1
  78. package/dist/esm/dynamicComponents/BlogDynamic.js +163 -0
  79. package/dist/esm/dynamicComponents/BlogDynamic.js.map +1 -0
  80. package/dist/esm/dynamicComponents/BlogHeaderDynamic.js +9 -0
  81. package/dist/esm/dynamicComponents/BlogHeaderDynamic.js.map +1 -0
  82. package/dist/esm/dynamicComponents/BlogLinkDynamic.js +31 -0
  83. package/dist/esm/dynamicComponents/BlogLinkDynamic.js.map +1 -0
  84. package/dist/esm/dynamicComponents/BlogSectionDynamic.js +46 -0
  85. package/dist/esm/dynamicComponents/BlogSectionDynamic.js.map +1 -0
  86. package/dist/esm/dynamicComponents/CalloutDynamic.js +10 -0
  87. package/dist/esm/dynamicComponents/CalloutDynamic.js.map +1 -0
  88. package/dist/esm/dynamicComponents/CodeBlockDynamic.js +34 -0
  89. package/dist/esm/dynamicComponents/CodeBlockDynamic.js.map +1 -0
  90. package/dist/esm/dynamicComponents/MermaidDynamic.js +55 -0
  91. package/dist/esm/dynamicComponents/MermaidDynamic.js.map +1 -0
  92. package/dist/esm/dynamicComponents/TableDynamic.js +14 -0
  93. package/dist/esm/dynamicComponents/TableDynamic.js.map +1 -0
  94. package/dist/esm/index.css +2 -0
  95. package/dist/esm/index.css.map +1 -0
  96. package/dist/esm/staticComponents/BlogLinkStatic.js +13 -0
  97. package/dist/esm/staticComponents/BlogLinkStatic.js.map +1 -0
  98. package/dist/esm/staticComponents/BlogSectionStatic.js +15 -0
  99. package/dist/esm/staticComponents/BlogSectionStatic.js.map +1 -0
  100. package/dist/esm/staticComponents/BlogStatic.js +9 -0
  101. package/dist/esm/staticComponents/BlogStatic.js.map +1 -0
  102. package/dist/esm/staticComponents/CodeBlockStatic.js +9 -0
  103. package/dist/esm/staticComponents/CodeBlockStatic.js.map +1 -0
  104. package/dist/esm/staticComponents/MermaidStatic.js +9 -0
  105. package/dist/esm/staticComponents/MermaidStatic.js.map +1 -0
  106. package/dist/esm/styles/Blog.module.scss.js +0 -4
  107. package/dist/esm/styles/Blog.module.scss.js.map +1 -1
  108. package/dist/esm/styles/BlogHeader.module.scss.js +0 -4
  109. package/dist/esm/styles/BlogHeader.module.scss.js.map +1 -1
  110. package/dist/esm/styles/BlogLink.module.scss.js +0 -4
  111. package/dist/esm/styles/BlogLink.module.scss.js.map +1 -1
  112. package/dist/esm/styles/BlogSection.module.scss.js +0 -4
  113. package/dist/esm/styles/BlogSection.module.scss.js.map +1 -1
  114. package/dist/esm/styles/Callout.module.scss.js +0 -4
  115. package/dist/esm/styles/Callout.module.scss.js.map +1 -1
  116. package/dist/esm/styles/CodeBlock.module.scss.js +1 -5
  117. package/dist/esm/styles/CodeBlock.module.scss.js.map +1 -1
  118. package/dist/esm/styles/Mermaid.module.scss.js +0 -4
  119. package/dist/esm/styles/Mermaid.module.scss.js.map +1 -1
  120. package/dist/esm/styles/Table.module.scss.js +0 -4
  121. package/dist/esm/styles/Table.module.scss.js.map +1 -1
  122. package/dist/types/components/Blog.d.ts +0 -4
  123. package/dist/types/components/Blog.d.ts.map +1 -1
  124. package/dist/types/components/BlogHeader.d.ts +2 -2
  125. package/dist/types/components/BlogHeader.d.ts.map +1 -1
  126. package/dist/types/components/BlogLink.d.ts +2 -2
  127. package/dist/types/components/BlogLink.d.ts.map +1 -1
  128. package/dist/types/components/BlogSection.d.ts +5 -4
  129. package/dist/types/components/BlogSection.d.ts.map +1 -1
  130. package/dist/types/components/Callout.d.ts +1 -1
  131. package/dist/types/components/Callout.d.ts.map +1 -1
  132. package/dist/types/components/CodeBlock.d.ts +2 -3
  133. package/dist/types/components/CodeBlock.d.ts.map +1 -1
  134. package/dist/types/components/Mermaid.d.ts.map +1 -1
  135. package/dist/types/components/Table.d.ts.map +1 -1
  136. package/dist/types/components/index.d.ts +0 -1
  137. package/dist/types/components/index.d.ts.map +1 -1
  138. package/dist/types/dynamicComponents/BlogDynamic.d.ts +12 -0
  139. package/dist/types/dynamicComponents/BlogDynamic.d.ts.map +1 -0
  140. package/dist/types/dynamicComponents/BlogHeaderDynamic.d.ts +8 -0
  141. package/dist/types/dynamicComponents/BlogHeaderDynamic.d.ts.map +1 -0
  142. package/dist/types/dynamicComponents/BlogLinkDynamic.d.ts +9 -0
  143. package/dist/types/dynamicComponents/BlogLinkDynamic.d.ts.map +1 -0
  144. package/dist/types/dynamicComponents/BlogSectionDynamic.d.ts +11 -0
  145. package/dist/types/dynamicComponents/BlogSectionDynamic.d.ts.map +1 -0
  146. package/dist/types/dynamicComponents/CalloutDynamic.d.ts +10 -0
  147. package/dist/types/dynamicComponents/CalloutDynamic.d.ts.map +1 -0
  148. package/dist/types/dynamicComponents/CodeBlockDynamic.d.ts +10 -0
  149. package/dist/types/dynamicComponents/CodeBlockDynamic.d.ts.map +1 -0
  150. package/dist/types/dynamicComponents/MermaidDynamic.d.ts +9 -0
  151. package/dist/types/dynamicComponents/MermaidDynamic.d.ts.map +1 -0
  152. package/dist/types/dynamicComponents/TableDynamic.d.ts +10 -0
  153. package/dist/types/dynamicComponents/TableDynamic.d.ts.map +1 -0
  154. package/dist/types/index.d.ts +0 -1
  155. package/dist/types/index.d.ts.map +1 -1
  156. package/dist/types/staticComponents/BlogLinkStatic.d.ts +9 -0
  157. package/dist/types/staticComponents/BlogLinkStatic.d.ts.map +1 -0
  158. package/dist/types/staticComponents/BlogSectionStatic.d.ts +10 -0
  159. package/dist/types/staticComponents/BlogSectionStatic.d.ts.map +1 -0
  160. package/dist/types/staticComponents/BlogStatic.d.ts +7 -0
  161. package/dist/types/staticComponents/BlogStatic.d.ts.map +1 -0
  162. package/dist/types/staticComponents/CodeBlockStatic.d.ts +9 -0
  163. package/dist/types/staticComponents/CodeBlockStatic.d.ts.map +1 -0
  164. package/dist/types/staticComponents/MermaidStatic.d.ts +9 -0
  165. package/dist/types/staticComponents/MermaidStatic.d.ts.map +1 -0
  166. package/package.json +8 -2
  167. package/src/components/Blog.tsx +7 -195
  168. package/src/components/BlogHeader.tsx +10 -23
  169. package/src/components/BlogLink.tsx +28 -58
  170. package/src/components/BlogSection.tsx +32 -57
  171. package/src/components/Callout.tsx +12 -11
  172. package/src/components/CodeBlock.tsx +23 -55
  173. package/src/components/Mermaid.tsx +21 -55
  174. package/src/components/Table.tsx +7 -34
  175. package/src/components/index.ts +0 -2
  176. package/src/dynamicComponents/BlogDynamic.tsx +257 -0
  177. package/src/dynamicComponents/BlogHeaderDynamic.tsx +32 -0
  178. package/src/dynamicComponents/BlogLinkDynamic.tsx +74 -0
  179. package/src/dynamicComponents/BlogSectionDynamic.tsx +90 -0
  180. package/src/dynamicComponents/CalloutDynamic.tsx +27 -0
  181. package/src/dynamicComponents/CodeBlockDynamic.tsx +76 -0
  182. package/src/dynamicComponents/MermaidDynamic.tsx +78 -0
  183. package/src/dynamicComponents/TableDynamic.tsx +54 -0
  184. package/src/index.ts +0 -3
  185. package/src/staticComponents/BlogLinkStatic.tsx +45 -0
  186. package/src/staticComponents/BlogSectionStatic.tsx +40 -0
  187. package/src/staticComponents/BlogStatic.tsx +16 -0
  188. package/src/staticComponents/CodeBlockStatic.tsx +34 -0
  189. package/src/staticComponents/MermaidStatic.tsx +26 -0
  190. package/src/styles/Blog.module.scss +17 -7
  191. package/src/styles/CodeBlock.module.scss +46 -41
  192. package/src/styles/CodeBlock.module.scss.d.ts +1 -0
  193. package/dist/cjs/node_modules/style-inject/dist/style-inject.es.js +0 -33
  194. package/dist/cjs/node_modules/style-inject/dist/style-inject.es.js.map +0 -1
  195. package/dist/esm/node_modules/style-inject/dist/style-inject.es.js +0 -29
  196. package/dist/esm/node_modules/style-inject/dist/style-inject.es.js.map +0 -1
@@ -1,10 +1,11 @@
1
1
  'use client';
2
2
 
3
- import { useCallback, useEffect, useRef, useState } from 'react';
3
+ import { lazy, Suspense } from 'react';
4
+ import MermaidStatic from '../staticComponents/MermaidStatic';
4
5
 
5
- import mermaid from 'mermaid';
6
-
7
- import styles from '../styles/Mermaid.module.scss';
6
+ const MermaidDynamic = lazy(
7
+ () => import('../dynamicComponents/MermaidDynamic')
8
+ );
8
9
 
9
10
  interface MermaidProperties {
10
11
  code: string;
@@ -13,65 +14,30 @@ interface MermaidProperties {
13
14
  hasMarginDown?: boolean;
14
15
  }
15
16
 
16
- mermaid.initialize({
17
- startOnLoad: false,
18
- theme: 'default',
19
- timeline: {
20
- useMaxWidth: true,
21
- diagramMarginX: 0,
22
- },
23
- flowchart: {
24
- useMaxWidth: true,
25
- diagramPadding: 0,
26
- },
27
- sequence: {
28
- useMaxWidth: true,
29
- diagramMarginX: 0,
30
- },
31
- });
32
-
33
17
  const Mermaid = ({
34
18
  code = '',
35
19
  id = '',
36
20
  hasMarginUp = false,
37
21
  hasMarginDown = false,
38
22
  }: MermaidProperties) => {
39
- const [enabled, setEnabled] = useState(false);
40
- const mermaidReference = useRef<HTMLDivElement>(null);
41
-
42
- const initializeMermaid = useCallback(async () => {
43
- try {
44
- if (!mermaidReference.current || !code) return;
45
- const { svg, bindFunctions } = await mermaid.render(
46
- `mermaid-diagram-${id}`,
47
- code
48
- );
49
- if (!svg) return;
50
- mermaidReference.current.innerHTML = svg || '';
51
- bindFunctions?.(mermaidReference.current);
52
- setEnabled(true);
53
- } catch (error) {
54
- console.error('Failed to render Mermaid diagram:', error);
55
- }
56
- }, [code, id]);
57
-
58
- useEffect(() => {
59
- if (!code || !mermaidReference.current) return;
60
- const timer = setTimeout(async () => {
61
- await initializeMermaid();
62
- }, 100);
63
- return () => clearTimeout(timer);
64
- }, [code, initializeMermaid]);
65
-
66
23
  return (
67
- <div
68
- className={`${styles.mermaid}
69
- ${hasMarginUp ? styles['margin-top--1'] : ''}
70
- ${hasMarginDown ? styles['margin-bottom--2'] : ''}`}
24
+ <Suspense
25
+ fallback={
26
+ <MermaidStatic
27
+ code={code}
28
+ id={id}
29
+ hasMarginUp={hasMarginUp}
30
+ hasMarginDown={hasMarginDown}
31
+ />
32
+ }
71
33
  >
72
- {enabled ? null : <p>Diagram Loading...</p>}
73
- <div ref={mermaidReference} id={id}></div>
74
- </div>
34
+ <MermaidDynamic
35
+ code={code}
36
+ id={id}
37
+ hasMarginUp={hasMarginUp}
38
+ hasMarginDown={hasMarginDown}
39
+ />
40
+ </Suspense>
75
41
  );
76
42
  };
77
43
 
@@ -1,5 +1,5 @@
1
1
  import { ReactNode } from 'react';
2
- import styles from '../styles/Table.module.scss';
2
+ import TableDynamic from '../dynamicComponents/TableDynamic';
3
3
 
4
4
  interface TableProperties {
5
5
  rows?: ReactNode[][];
@@ -14,40 +14,13 @@ const Table = ({
14
14
  hasMarginUp = false,
15
15
  hasMarginDown = false,
16
16
  }: TableProperties) => {
17
- const columnCount = headers.length;
18
-
19
17
  return (
20
- <div
21
- className={`${styles.table}
22
- ${hasMarginUp ? styles['margin-top--1'] : ''}
23
- ${hasMarginDown ? styles['margin-bottom--2'] : ''}`}
24
- style={{
25
- gridTemplateColumns: `repeat(${columnCount}, 1fr)`,
26
- }}
27
- >
28
- <div className={`${styles['table__header']}`}>
29
- {headers.map((header, index) => (
30
- <div
31
- key={typeof header === 'string' ? header : index}
32
- className={`${styles['table__header__cell']}`}
33
- >
34
- {header}
35
- </div>
36
- ))}
37
- </div>
38
- {rows.map((row, index) => (
39
- <div key={index} className={`${styles['table__row']}`}>
40
- {row.map((cell, cellIndex) => (
41
- <div
42
- key={typeof cell === 'string' ? cell : cellIndex}
43
- className={`${styles['table__row__cell']}`}
44
- >
45
- {cell}
46
- </div>
47
- ))}
48
- </div>
49
- ))}
50
- </div>
18
+ <TableDynamic
19
+ rows={rows}
20
+ headers={headers}
21
+ hasMarginUp={hasMarginUp}
22
+ hasMarginDown={hasMarginDown}
23
+ />
51
24
  );
52
25
  };
53
26
 
@@ -6,5 +6,3 @@ export { default as Callout } from './Callout';
6
6
  export { default as Mermaid } from './Mermaid';
7
7
  export { default as BlogLink } from './BlogLink';
8
8
  export { default as Table } from './Table';
9
-
10
- export type { ForwardedReference } from './Blog';
@@ -0,0 +1,257 @@
1
+ 'use client';
2
+
3
+ import {
4
+ Children,
5
+ cloneElement,
6
+ isValidElement,
7
+ useCallback,
8
+ useEffect,
9
+ useRef,
10
+ useState,
11
+ } from 'react';
12
+
13
+ import type { MouseEvent, ReactNode, RefAttributes } from 'react';
14
+
15
+ import styles from '../styles/Blog.module.scss';
16
+
17
+ interface BlogProperties {
18
+ children: ReactNode;
19
+ title?: string;
20
+ }
21
+
22
+ export interface ForwardedReference {
23
+ parentRef: HTMLDivElement;
24
+ childRefs: HTMLDivElement[];
25
+ }
26
+
27
+ interface SectionReferenceValue {
28
+ el: HTMLElement;
29
+ title: string;
30
+ isSubSection: boolean;
31
+ }
32
+
33
+ type SectionReference = Map<string, SectionReferenceValue>;
34
+
35
+ interface CategoryTitleValue extends SectionReferenceValue {
36
+ lastUpdatedAt: number;
37
+ }
38
+
39
+ type CategoryTitle = Map<string, CategoryTitleValue>;
40
+
41
+ type AddPaddingTopTimerReference = ReturnType<typeof setTimeout> | null;
42
+
43
+ const Blog = ({ children, title = 'In this article' }: BlogProperties) => {
44
+ const addPaddingTopTimerReference = useRef<AddPaddingTopTimerReference>(null);
45
+ const highlightCategoryTimerReference =
46
+ useRef<AddPaddingTopTimerReference>(null);
47
+
48
+ const clearTimers = (
49
+ addPaddingTopTimerReference_: AddPaddingTopTimerReference,
50
+ highlightCategoryTimerReference_: AddPaddingTopTimerReference
51
+ ) => {
52
+ if (addPaddingTopTimerReference_) {
53
+ clearTimeout(addPaddingTopTimerReference_);
54
+ }
55
+ if (highlightCategoryTimerReference_) {
56
+ clearTimeout(highlightCategoryTimerReference_);
57
+ }
58
+ };
59
+
60
+ const sectionReferences = useRef<SectionReference>(new Map());
61
+ const [categoryTitles, setCategoryTitles] = useState<CategoryTitle>(
62
+ new Map()
63
+ );
64
+ const [visibleTitle, setVisibleTitle] = useState<string | null>(null);
65
+ const updateTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
66
+
67
+ const sortByDomPosition = useCallback(
68
+ ([, a]: [string, SectionReferenceValue], [, b]: [string, SectionReferenceValue]) => {
69
+ const position = a.el.compareDocumentPosition(b.el);
70
+ if (position & Node.DOCUMENT_POSITION_FOLLOWING) {
71
+ return -1; // a comes before b
72
+ } else if (position & Node.DOCUMENT_POSITION_PRECEDING) {
73
+ return 1; // b comes before a
74
+ }
75
+ return 0;
76
+ },
77
+ []
78
+ );
79
+
80
+ const updateCategoryTitles = useCallback(() => {
81
+ const now = Date.now();
82
+ const newCategoryTitles = new Map<string, CategoryTitleValue>();
83
+
84
+ // Sort sections by their DOM position to maintain correct order
85
+ const sectionsArray = Array.from(sectionReferences.current.entries());
86
+ sectionsArray.sort(sortByDomPosition);
87
+
88
+ let firstSectionId: string | null = null;
89
+ for (const [id, { title, el, isSubSection }] of sectionsArray) {
90
+ if (!firstSectionId) {
91
+ firstSectionId = id;
92
+ }
93
+ newCategoryTitles.set(id, {
94
+ el,
95
+ title,
96
+ lastUpdatedAt: now,
97
+ isSubSection,
98
+ });
99
+ }
100
+
101
+ if (newCategoryTitles.size > 0) {
102
+ setCategoryTitles(newCategoryTitles);
103
+ if (!visibleTitle) {
104
+ setVisibleTitle(firstSectionId);
105
+ }
106
+ }
107
+ }, [visibleTitle, sortByDomPosition]);
108
+
109
+ const debounceUpdateCategoryTitles = useCallback(() => {
110
+ // Clear existing timer and set a new one to batch updates
111
+ if (updateTimerRef.current) {
112
+ clearTimeout(updateTimerRef.current);
113
+ }
114
+ updateTimerRef.current = setTimeout(() => {
115
+ updateCategoryTitles();
116
+ }, 50);
117
+ }, [updateCategoryTitles]);
118
+
119
+ useEffect(() => {
120
+ const observers = new Map<string, IntersectionObserver>();
121
+ for (const [id, { el }] of categoryTitles) {
122
+ const observer = new IntersectionObserver(
123
+ ([entry]) => {
124
+ if (!entry.isIntersecting) return;
125
+ setVisibleTitle(visibleId => {
126
+ if (visibleId === id && !entry.isIntersecting) return null;
127
+ if (entry.isIntersecting) return id;
128
+ return visibleId;
129
+ });
130
+ },
131
+ { threshold: 0.1 }
132
+ );
133
+ observers.set(id, observer);
134
+ observer.observe(el);
135
+ }
136
+ return () => {
137
+ for (const observer of observers.values()) {
138
+ observer.disconnect();
139
+ }
140
+ };
141
+ }, [categoryTitles.size]);
142
+
143
+ useEffect(() => {
144
+ return () => {
145
+ clearTimers(
146
+ addPaddingTopTimerReference.current,
147
+ highlightCategoryTimerReference.current
148
+ );
149
+ if (updateTimerRef.current) {
150
+ clearTimeout(updateTimerRef.current);
151
+ }
152
+ };
153
+ }, []);
154
+
155
+ const handleSectionReference = useCallback((element: ForwardedReference) => {
156
+ if (!element) return;
157
+ const { parentRef, childRefs } = element;
158
+
159
+ // Add parent section reference
160
+ if (parentRef) {
161
+ const id = parentRef.dataset.id;
162
+ const title = parentRef.dataset.title;
163
+ if (id && title) {
164
+ sectionReferences.current.set(id, {
165
+ el: parentRef,
166
+ title,
167
+ isSubSection: false,
168
+ });
169
+ }
170
+ }
171
+
172
+ // Add child section references
173
+ if (Array.isArray(childRefs)) {
174
+ for (const childRef of childRefs) {
175
+ if (!childRef) continue;
176
+ const id = childRef.dataset.id;
177
+ const title = childRef.dataset.title;
178
+ if (id && title) {
179
+ sectionReferences.current.set(id, {
180
+ el: childRef,
181
+ title,
182
+ isSubSection: true,
183
+ });
184
+ }
185
+ }
186
+ }
187
+
188
+ debounceUpdateCategoryTitles();
189
+ }, [debounceUpdateCategoryTitles]);
190
+
191
+ const handleClickCategoryTitle = (
192
+ error: MouseEvent<HTMLParagraphElement>
193
+ ) => {
194
+ const id = error.currentTarget.dataset.id;
195
+ const index = error.currentTarget.dataset.idx;
196
+ if (!id || !index) return;
197
+
198
+ const { el } = categoryTitles.get(id) || {};
199
+ if (!el) return;
200
+
201
+ const top = el.getBoundingClientRect().top + document.body.scrollTop - 100;
202
+ document.body.scrollTo({
203
+ top,
204
+ behavior: 'smooth',
205
+ });
206
+
207
+ const timer = setTimeout(() => {
208
+ setVisibleTitle(id);
209
+ clearTimeout(timer);
210
+ }, 1000);
211
+ };
212
+
213
+
214
+ return (
215
+ <div className={styles.blog}>
216
+ <div className={styles['blog__content']}>
217
+ {Children.map(children, child => {
218
+ if (!isValidElement(child)) return child;
219
+ return cloneElement(child, {
220
+ ref: handleSectionReference,
221
+ } as RefAttributes<ForwardedReference>);
222
+ })}
223
+ </div>
224
+ <div className={styles['blog__sidebar']}>
225
+ <p
226
+ className={`${styles['margin-bottom--3']} ${styles['category__header']}`}
227
+ >
228
+ {title}
229
+ </p>
230
+ {[...categoryTitles].map(
231
+ ([id, { title, isSubSection }], index, array) => {
232
+ const isNextSectionSubSection = array[index + 1]?.[1]?.isSubSection;
233
+ return (
234
+ <p
235
+ key={id}
236
+ data-idx={index}
237
+ data-id={id}
238
+ className={`${styles['category__title']} ${
239
+ id === visibleTitle ? styles['category__title--active'] : ''
240
+ } ${isSubSection ? styles['category__title--sub'] : ''} ${
241
+ isSubSection && !isNextSectionSubSection
242
+ ? styles['margin-bottom-imp--2']
243
+ : ''
244
+ }`}
245
+ onClick={handleClickCategoryTitle}
246
+ >
247
+ {title}
248
+ </p>
249
+ );
250
+ }
251
+ )}
252
+ </div>
253
+ </div>
254
+ );
255
+ };
256
+
257
+ export default Blog;
@@ -0,0 +1,32 @@
1
+ import { Fragment } from 'react';
2
+
3
+ import styles from '../styles/BlogHeader.module.scss';
4
+
5
+ interface BlogProperties {
6
+ title: string[];
7
+ desc: string[];
8
+ isDescCite?: boolean;
9
+ }
10
+
11
+ const renderLineBreaks = (array: string[]) =>
12
+ array.map((element, index, array) => (
13
+ <Fragment key={element}>
14
+ {element}
15
+ {index === array.length - 1 ? null : <br />}
16
+ </Fragment>
17
+ ));
18
+
19
+ const BlogHeader = ({ title, desc, isDescCite = true }: BlogProperties) => (
20
+ <>
21
+ <h1 className={`${styles['blog-header']}`}>{renderLineBreaks(title)}</h1>
22
+ {isDescCite ? (
23
+ <cite className={`${styles['blog-date']}`}>
24
+ {renderLineBreaks(desc)}
25
+ </cite>
26
+ ) : (
27
+ <p className={`${styles['blog-date']}`}>{renderLineBreaks(desc)}</p>
28
+ )}
29
+ </>
30
+ );
31
+
32
+ export default BlogHeader;
@@ -0,0 +1,74 @@
1
+ 'use client';
2
+
3
+ import { useState } from 'react';
4
+
5
+ import { animated, useSpring } from '@react-spring/web';
6
+
7
+ import styles from '../styles/BlogLink.module.scss';
8
+
9
+ import { generateUrlForBlogTitle } from '../utils';
10
+
11
+ interface Properties {
12
+ title: string;
13
+ desc?: string;
14
+ isInProgress?: boolean;
15
+ href?: string;
16
+ }
17
+
18
+ const DEFAULT_LINE_END = 18;
19
+ const DEFAULT_POLYLINE_POINTS = '12 5, 19 12, 12 19';
20
+ const MOVED_POLYLINE_POINTS = '15 5, 22 12, 15 19';
21
+
22
+ const BlogLink = ({ title = '', desc = '', isInProgress = false, href }: Properties) => {
23
+ const [isHovered, setIsHovered] = useState(false);
24
+
25
+ const link = href || `/blog/${generateUrlForBlogTitle(title)}`;
26
+
27
+ const svgColor = useSpring({
28
+ stroke: isHovered ? '#4242fa' : 'transparent',
29
+ });
30
+
31
+ const polyLine = useSpring({
32
+ points: isHovered ? MOVED_POLYLINE_POINTS : DEFAULT_POLYLINE_POINTS,
33
+ config: { duration: 200 },
34
+ });
35
+
36
+ const lineEnd = useSpring({
37
+ x2: isHovered ? `${DEFAULT_LINE_END + 2}` : `${DEFAULT_LINE_END}`,
38
+ config: { duration: 200 },
39
+ });
40
+
41
+ if (isInProgress) return null;
42
+
43
+ return (
44
+ <a
45
+ className={styles['blog-link']}
46
+ onMouseEnter={() => setIsHovered(true)}
47
+ onMouseLeave={() => setIsHovered(false)}
48
+ href={link}
49
+ rel="noopener noreferrer"
50
+ >
51
+ <h6 className={styles['blog-link__title']}>{title}</h6>
52
+ <p className={styles['blog-link__description']}>{desc}</p>
53
+ <div className={styles['blog-link__read-more']}>
54
+ <p>Read More</p>
55
+ <animated.svg
56
+ xmlns="http://www.w3.org/2000/svg"
57
+ width="18px"
58
+ height="18px"
59
+ viewBox="0 0 24 24"
60
+ fill="none"
61
+ strokeWidth="2"
62
+ strokeLinecap="round"
63
+ strokeLinejoin="round"
64
+ style={svgColor as any}
65
+ >
66
+ <animated.line x1="5" y1="12" y2="12" x2={lineEnd.x2 as any}></animated.line>
67
+ <animated.polyline points={polyLine.points as any} />
68
+ </animated.svg>
69
+ </div>
70
+ </a>
71
+ );
72
+ };
73
+
74
+ export default BlogLink;
@@ -0,0 +1,90 @@
1
+ import {
2
+ Children,
3
+ cloneElement,
4
+ forwardRef,
5
+ isValidElement,
6
+ useImperativeHandle,
7
+ useRef,
8
+ } from 'react';
9
+
10
+ import type { ReactNode, RefAttributes } from 'react';
11
+
12
+ import styles from '../styles/BlogSection.module.scss';
13
+
14
+ import type { ForwardedReference } from './BlogDynamic';
15
+ import { generateIdForBlogTitle } from '../utils';
16
+
17
+ interface BlogProperties {
18
+ title?: string;
19
+ category?: string;
20
+ children?: ReactNode;
21
+ increaseMarginBottom?: boolean;
22
+ }
23
+
24
+ const BlogSection = forwardRef<ForwardedReference, BlogProperties>(
25
+ (
26
+ {
27
+ title = '',
28
+ category = '',
29
+ children = null,
30
+ increaseMarginBottom = false,
31
+ }: BlogProperties,
32
+ forwardedReference
33
+ ) => {
34
+ const titleWithCategory = category ? `${category} - ${title}` : title;
35
+ const id = generateIdForBlogTitle(titleWithCategory);
36
+
37
+ const parentReference = useRef<ForwardedReference['parentRef']>(null);
38
+ const childReferences = useRef<ForwardedReference['childRefs']>([]);
39
+ const imperativeHandleRef = useRef<ForwardedReference | null>(null);
40
+
41
+ useImperativeHandle(forwardedReference, () => {
42
+ const handle = {
43
+ parentRef: parentReference.current!,
44
+ childRefs: childReferences.current!,
45
+ };
46
+ imperativeHandleRef.current = handle;
47
+ return handle;
48
+ });
49
+
50
+ const handleChildReferences = (element: ForwardedReference | null) => {
51
+ if (!element) return;
52
+ const { parentRef: subParentReference } = element;
53
+ if (!subParentReference) return;
54
+ childReferences.current.push(subParentReference);
55
+
56
+ // Re-trigger parent ref callback with updated children
57
+ if (typeof forwardedReference === 'function' && imperativeHandleRef.current) {
58
+ forwardedReference(imperativeHandleRef.current);
59
+ }
60
+ };
61
+
62
+ return (
63
+ <div
64
+ className={`${styles['blog-section']}
65
+ ${
66
+ increaseMarginBottom
67
+ ? styles['margin-bottom--9']
68
+ : styles['margin-bottom--6']
69
+ }`}
70
+ data-title={title}
71
+ data-id={id}
72
+ ref={parentReference}
73
+ >
74
+ {title ? (
75
+ <h4 className={styles['blog-section__title']}>{title}</h4>
76
+ ) : null}
77
+ {Children.map(children, child => {
78
+ if (!isValidElement(child)) return child;
79
+ return cloneElement(child, {
80
+ ref: handleChildReferences,
81
+ } as RefAttributes<ForwardedReference>);
82
+ })}
83
+ </div>
84
+ );
85
+ }
86
+ );
87
+
88
+ BlogSection.displayName = 'BlogSection';
89
+
90
+ export default BlogSection;
@@ -0,0 +1,27 @@
1
+ import type { ReactNode } from 'react';
2
+
3
+ import styles from '../styles/Callout.module.scss';
4
+
5
+ interface CalloutProperties {
6
+ children?: ReactNode;
7
+ type: 'info' | 'warning' | 'error' | 'success';
8
+ hasMarginUp?: boolean;
9
+ hasMarginDown?: boolean;
10
+ }
11
+
12
+ const Callout = ({ children, type = 'info',
13
+ hasMarginUp = false,
14
+ hasMarginDown = false
15
+ }: CalloutProperties) => {
16
+ const className = `${styles.callout} ${styles[`callout--${type}`]} ${
17
+ hasMarginUp ? styles['margin-top--1'] : ''
18
+ } ${hasMarginDown ? styles['margin-bottom--2'] : ''}`;
19
+ return (
20
+ <div className={className}>
21
+ <div className={styles.callout__icon}/>
22
+ <div className={styles.callout__wrapper}>{children}</div>
23
+ </div>
24
+ );
25
+ };
26
+
27
+ export default Callout;