@portosaur/theme 0.1.5 → 0.3.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.
- package/package.json +12 -3
- package/src/plugins/theme.mjs +2 -0
- package/theme/DocCategoryGeneratedIndexPage/index.jsx +4 -10
- package/theme/MDXComponents.jsx +3 -2
- package/theme/Root.jsx +1 -1
- package/theme/components/AboutSection/index.jsx +89 -249
- package/theme/components/ContactSection/index.jsx +72 -153
- package/theme/components/ExperienceSection/index.jsx +35 -106
- package/theme/components/HeroSection/index.jsx +64 -186
- package/theme/components/Indent/index.jsx +10 -0
- package/theme/components/NavArrow/index.jsx +38 -55
- package/theme/components/NoteCards/index.jsx +110 -0
- package/theme/components/Preview/components/FeedbackStates.jsx +45 -190
- package/theme/components/Preview/components/FileTabs.jsx +17 -24
- package/theme/components/Preview/components/PreviewContent.jsx +37 -62
- package/theme/components/Preview/components/PreviewHeader.jsx +146 -380
- package/theme/components/Preview/components/Triggers/Pv.jsx +50 -78
- package/theme/components/Preview/components/Triggers/SrcPv.jsx +16 -47
- package/theme/components/Preview/components/Triggers/index.jsx +2 -2
- package/theme/components/Preview/components/ViewerWindow.jsx +160 -268
- package/theme/components/Preview/index.jsx +3 -3
- package/theme/components/Preview/renderers/CodeRenderer.jsx +81 -109
- package/theme/components/Preview/renderers/ImageRenderer.jsx +30 -67
- package/theme/components/Preview/renderers/PdfRenderer.jsx +31 -52
- package/theme/components/Preview/renderers/WebRenderer.jsx +18 -32
- package/theme/components/Preview/state/index.jsx +46 -30
- package/theme/components/ProjectsSection/index.jsx +278 -573
- package/theme/components/ScrollToTop/index.jsx +93 -0
- package/theme/components/ScrollToTop/styles.module.css +97 -0
- package/theme/components/SocialLinks/index.jsx +43 -55
- package/theme/components/Tooltip/index.jsx +85 -43
- package/theme/components/Tooltip/styles.module.css +13 -3
- package/theme/components/TopicList/index.jsx +31 -0
- package/theme/css/animations.css +61 -0
- package/theme/css/custom.css +16 -253
- package/theme/css/overrides/base.css +37 -0
- package/theme/css/overrides/footer.css +14 -0
- package/theme/css/overrides/markdown.css +62 -0
- package/theme/css/overrides/navbar.css +37 -0
- package/theme/css/overrides/pagination.css +64 -0
- package/theme/css/overrides/search.css +28 -0
- package/theme/css/{catppuccin.css → overrides/variables.css} +1 -1
- package/theme/pages/index.jsx +25 -87
- package/theme/pages/notes.jsx +27 -104
- package/theme/pages/tasks.jsx +293 -904
- package/theme/components/NoteIndex/index.jsx +0 -182
- package/theme/css/bootstrap.css +0 -5
- /package/theme/components/{NoteIndex → NoteCards}/styles.module.css +0 -0
- /package/theme/css/{tasks.css → tasks.module.css} +0 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { useState, useEffect, useRef } from "react";
|
|
2
|
+
import { IoIosArrowUp } from "react-icons/io";
|
|
3
|
+
import styles from "./styles.module.css";
|
|
4
|
+
|
|
5
|
+
export default function ScrollToTop({ hideDelay = 1500 }) {
|
|
6
|
+
const [isVisible, setIsVisible] = useState(false);
|
|
7
|
+
const [isHovering, setIsHovering] = useState(false);
|
|
8
|
+
const timeoutRef = useRef(null);
|
|
9
|
+
const lastScrollTopRef = useRef(0);
|
|
10
|
+
|
|
11
|
+
const startHideTimer = () => {
|
|
12
|
+
// Clear any existing timeout
|
|
13
|
+
if (timeoutRef.current) {
|
|
14
|
+
clearTimeout(timeoutRef.current);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Only start timer if not hovering
|
|
18
|
+
if (!isHovering) {
|
|
19
|
+
timeoutRef.current = setTimeout(() => {
|
|
20
|
+
setIsVisible(false);
|
|
21
|
+
}, hideDelay);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
const handleScroll = () => {
|
|
27
|
+
const scrollTop =
|
|
28
|
+
window.pageYOffset || document.documentElement.scrollTop;
|
|
29
|
+
const isScrollingUp = scrollTop < lastScrollTopRef.current;
|
|
30
|
+
|
|
31
|
+
// Save the current scroll position
|
|
32
|
+
lastScrollTopRef.current = scrollTop;
|
|
33
|
+
|
|
34
|
+
// Show button when scrolling up past threshold
|
|
35
|
+
if (isScrollingUp && scrollTop > 300) {
|
|
36
|
+
setIsVisible(true);
|
|
37
|
+
startHideTimer();
|
|
38
|
+
} else {
|
|
39
|
+
setIsVisible(false);
|
|
40
|
+
if (timeoutRef.current) {
|
|
41
|
+
clearTimeout(timeoutRef.current);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// Set up event listener
|
|
47
|
+
window.addEventListener("scroll", handleScroll, { passive: true });
|
|
48
|
+
|
|
49
|
+
// Clean up
|
|
50
|
+
return () => {
|
|
51
|
+
window.removeEventListener("scroll", handleScroll);
|
|
52
|
+
if (timeoutRef.current) {
|
|
53
|
+
clearTimeout(timeoutRef.current);
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
}, [hideDelay, isHovering]);
|
|
57
|
+
|
|
58
|
+
const handleMouseEnter = () => {
|
|
59
|
+
setIsHovering(true);
|
|
60
|
+
if (timeoutRef.current) {
|
|
61
|
+
clearTimeout(timeoutRef.current);
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const handleMouseLeave = () => {
|
|
66
|
+
setIsHovering(false);
|
|
67
|
+
startHideTimer();
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const scrollToTop = () => {
|
|
71
|
+
const prefersReducedMotion = window.matchMedia(
|
|
72
|
+
"(prefers-reduced-motion: reduce)",
|
|
73
|
+
).matches;
|
|
74
|
+
|
|
75
|
+
window.scrollTo({
|
|
76
|
+
top: 0,
|
|
77
|
+
behavior: prefersReducedMotion ? "auto" : "smooth",
|
|
78
|
+
});
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
<button
|
|
83
|
+
className={`${styles.scrollToTop} ${isVisible ? styles.visible : ""}`}
|
|
84
|
+
onClick={scrollToTop}
|
|
85
|
+
onMouseEnter={handleMouseEnter}
|
|
86
|
+
onMouseLeave={handleMouseLeave}
|
|
87
|
+
aria-label="Scroll to top"
|
|
88
|
+
title="Scroll to top"
|
|
89
|
+
>
|
|
90
|
+
<IoIosArrowUp />
|
|
91
|
+
</button>
|
|
92
|
+
);
|
|
93
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
.scrollToTop {
|
|
2
|
+
position: fixed;
|
|
3
|
+
bottom: 30px;
|
|
4
|
+
right: 30px;
|
|
5
|
+
background-color: var(--ifm-color-primary);
|
|
6
|
+
color: var(--ifm-background-color);
|
|
7
|
+
width: 50px;
|
|
8
|
+
height: 50px;
|
|
9
|
+
border-radius: 50%;
|
|
10
|
+
display: flex;
|
|
11
|
+
align-items: center;
|
|
12
|
+
justify-content: center;
|
|
13
|
+
cursor: pointer;
|
|
14
|
+
box-shadow: var(--ifm-global-shadow-md);
|
|
15
|
+
border: none;
|
|
16
|
+
z-index: 100;
|
|
17
|
+
opacity: 0;
|
|
18
|
+
transform: translateY(20px) scale(0.9);
|
|
19
|
+
pointer-events: none;
|
|
20
|
+
transition:
|
|
21
|
+
opacity 0.3s ease,
|
|
22
|
+
transform 0.3s ease,
|
|
23
|
+
background-color 0.3s ease;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.scrollToTop.visible {
|
|
27
|
+
opacity: 1;
|
|
28
|
+
transform: translateY(0) scale(1);
|
|
29
|
+
pointer-events: auto;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.scrollToTop:hover {
|
|
33
|
+
background-color: var(--ifm-color-primary-dark);
|
|
34
|
+
transform: translateY(-3px) scale(1.05);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.scrollToTop:active {
|
|
38
|
+
transform: translateY(-1px) scale(1.02);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.scrollToTop svg {
|
|
42
|
+
width: 28px;
|
|
43
|
+
height: 28px;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.scrollToTop:hover svg {
|
|
47
|
+
transform: scale(1.1);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/* Responsive adjustments */
|
|
51
|
+
@media (max-width: 768px) {
|
|
52
|
+
.scrollToTop {
|
|
53
|
+
width: 45px;
|
|
54
|
+
height: 45px;
|
|
55
|
+
bottom: 25px;
|
|
56
|
+
right: 25px;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.scrollToTop svg {
|
|
60
|
+
width: 24px;
|
|
61
|
+
height: 24px;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
@media (max-width: 480px) {
|
|
66
|
+
.scrollToTop {
|
|
67
|
+
width: 40px;
|
|
68
|
+
height: 40px;
|
|
69
|
+
bottom: 20px;
|
|
70
|
+
right: 50%;
|
|
71
|
+
transform: translateX(50%) translateY(20px) scale(0.9);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.scrollToTop.visible {
|
|
75
|
+
transform: translateX(50%) scale(1);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.scrollToTop:hover {
|
|
79
|
+
transform: translateX(50%) translateY(-3px) scale(1.05);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.scrollToTop:active {
|
|
83
|
+
transform: translateX(50%) translateY(-1px) scale(1.02);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.scrollToTop svg {
|
|
87
|
+
width: 22px;
|
|
88
|
+
height: 22px;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/* Accessibility preferences */
|
|
93
|
+
@media (prefers-reduced-motion: reduce) {
|
|
94
|
+
.scrollToTop {
|
|
95
|
+
transition: none;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -5,20 +5,27 @@ import useDocusaurusContext from "@docusaurus/useDocusaurusContext";
|
|
|
5
5
|
import useIsBrowser from "@docusaurus/useIsBrowser";
|
|
6
6
|
import Tooltip from "../Tooltip";
|
|
7
7
|
import { iconMap } from "../../config/iconMappings";
|
|
8
|
+
|
|
8
9
|
const DEFAULT_ICON = FaQuestionCircle;
|
|
9
10
|
const DEFAULT_COLOR = "var(--ifm-color-primary)";
|
|
11
|
+
|
|
10
12
|
export default function SocialIcons({ showAll = false, links = null }) {
|
|
11
13
|
const { siteConfig } = useDocusaurusContext();
|
|
12
14
|
const { customFields } = siteConfig;
|
|
13
15
|
const isBrowser = useIsBrowser();
|
|
14
16
|
const [animationDelays, setAnimationDelays] = useState({});
|
|
15
|
-
|
|
17
|
+
|
|
18
|
+
const allSocialLinks = customFields.socialSection?.links || [];
|
|
16
19
|
const socialLinks = useMemo(() => {
|
|
17
20
|
if (links) return links;
|
|
18
21
|
return showAll ? allSocialLinks : allSocialLinks.filter((link) => link.pin);
|
|
19
22
|
}, [allSocialLinks, showAll, links]);
|
|
23
|
+
|
|
20
24
|
const calculateDelays = useCallback(() => {
|
|
21
|
-
if (!isBrowser)
|
|
25
|
+
if (!isBrowser) {
|
|
26
|
+
return {};
|
|
27
|
+
}
|
|
28
|
+
|
|
22
29
|
const isTablet = window.innerWidth <= 768;
|
|
23
30
|
const isMobile = window.innerWidth <= 480;
|
|
24
31
|
const delays = {};
|
|
@@ -29,15 +36,15 @@ export default function SocialIcons({ showAll = false, links = null }) {
|
|
|
29
36
|
});
|
|
30
37
|
return delays;
|
|
31
38
|
}, [isBrowser, socialLinks]);
|
|
39
|
+
|
|
32
40
|
useEffect(() => {
|
|
33
41
|
if (!isBrowser) return;
|
|
34
42
|
setAnimationDelays(calculateDelays());
|
|
35
|
-
const handleResize = () =>
|
|
36
|
-
setAnimationDelays(calculateDelays());
|
|
37
|
-
};
|
|
43
|
+
const handleResize = () => setAnimationDelays(calculateDelays());
|
|
38
44
|
window.addEventListener("resize", handleResize);
|
|
39
45
|
return () => window.removeEventListener("resize", handleResize);
|
|
40
46
|
}, [isBrowser, calculateDelays]);
|
|
47
|
+
|
|
41
48
|
const getIconDetails = (iconName) => {
|
|
42
49
|
if (!iconName) {
|
|
43
50
|
return { icon: DEFAULT_ICON, color: DEFAULT_COLOR };
|
|
@@ -52,64 +59,45 @@ export default function SocialIcons({ showAll = false, links = null }) {
|
|
|
52
59
|
color: iconDetails.color || DEFAULT_COLOR,
|
|
53
60
|
};
|
|
54
61
|
};
|
|
62
|
+
|
|
55
63
|
if (socialLinks.length === 0) {
|
|
56
64
|
return null;
|
|
57
65
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
{
|
|
61
|
-
|
|
62
|
-
children: socialLinks.map((social, index) => {
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<div className={styles.socialIcons}>
|
|
69
|
+
{socialLinks.map((social, index) => {
|
|
63
70
|
const { icon: IconComponent, color: iconColor } = getIconDetails(
|
|
64
71
|
social.icon || social.name,
|
|
65
72
|
);
|
|
66
73
|
const href = social.url || "#";
|
|
67
74
|
const displayColor = social.color || iconColor;
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
false,
|
|
93
|
-
undefined,
|
|
94
|
-
this,
|
|
95
|
-
),
|
|
96
|
-
},
|
|
97
|
-
undefined,
|
|
98
|
-
false,
|
|
99
|
-
undefined,
|
|
100
|
-
this,
|
|
101
|
-
),
|
|
102
|
-
},
|
|
103
|
-
index,
|
|
104
|
-
false,
|
|
105
|
-
undefined,
|
|
106
|
-
this,
|
|
75
|
+
|
|
76
|
+
return (
|
|
77
|
+
<Tooltip
|
|
78
|
+
key={index}
|
|
79
|
+
msg={social.desc || social.name || social.icon || "Link"}
|
|
80
|
+
position="top"
|
|
81
|
+
bg={displayColor}
|
|
82
|
+
underline={false}
|
|
83
|
+
gap={17}
|
|
84
|
+
>
|
|
85
|
+
<a
|
|
86
|
+
href={href}
|
|
87
|
+
target="_blank"
|
|
88
|
+
rel="noopener noreferrer"
|
|
89
|
+
className={styles.socialLink}
|
|
90
|
+
style={{
|
|
91
|
+
"--hover-color": displayColor,
|
|
92
|
+
animationDelay: animationDelays[index] || "0s",
|
|
93
|
+
}}
|
|
94
|
+
aria-label={social.name || social.icon || "social link"}
|
|
95
|
+
>
|
|
96
|
+
<IconComponent size={24} />
|
|
97
|
+
</a>
|
|
98
|
+
</Tooltip>
|
|
107
99
|
);
|
|
108
|
-
})
|
|
109
|
-
|
|
110
|
-
undefined,
|
|
111
|
-
false,
|
|
112
|
-
undefined,
|
|
113
|
-
this,
|
|
100
|
+
})}
|
|
101
|
+
</div>
|
|
114
102
|
);
|
|
115
103
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { useState, useRef, useCallback, useEffect } from "react";
|
|
2
2
|
import { createPortal } from "react-dom";
|
|
3
3
|
import styles from "./styles.module.css";
|
|
4
|
+
|
|
4
5
|
export default function Tooltip({
|
|
5
6
|
children,
|
|
6
7
|
msg,
|
|
@@ -17,9 +18,13 @@ export default function Tooltip({
|
|
|
17
18
|
"Tooltip: 'msg' prop is required to display tooltip content.",
|
|
18
19
|
);
|
|
19
20
|
}
|
|
21
|
+
|
|
20
22
|
const [isVisible, setIsVisible] = useState(false);
|
|
21
|
-
const [coords, setCoords] = useState({ top: 0, left: 0 });
|
|
23
|
+
const [coords, setCoords] = useState({ top: 0, left: 0, originalLeft: 0 });
|
|
24
|
+
const [arrowOffset, setArrowOffset] = useState(0);
|
|
22
25
|
const containerRef = useRef(null);
|
|
26
|
+
const tooltipRef = useRef(null);
|
|
27
|
+
|
|
23
28
|
const tooltipStyle = {
|
|
24
29
|
...(bg && { "--tooltip-color": bg }),
|
|
25
30
|
...(color && { "--tooltip-text-color": color }),
|
|
@@ -27,6 +32,7 @@ export default function Tooltip({
|
|
|
27
32
|
bg && { "--tooltip-text-color": "var(--ifm-font-color-base-inverse)" }),
|
|
28
33
|
...(shadow && { "--tooltip-shadow": shadow }),
|
|
29
34
|
};
|
|
35
|
+
|
|
30
36
|
const show = useCallback(() => {
|
|
31
37
|
if (!containerRef.current || !containerRef.current.children[0]) return;
|
|
32
38
|
const rect = containerRef.current.children[0].getBoundingClientRect();
|
|
@@ -51,54 +57,90 @@ export default function Tooltip({
|
|
|
51
57
|
left = rect.left + rect.width / 2;
|
|
52
58
|
break;
|
|
53
59
|
}
|
|
54
|
-
setCoords({ top, left });
|
|
60
|
+
setCoords({ top, left, originalLeft: left });
|
|
61
|
+
setArrowOffset(0);
|
|
55
62
|
setIsVisible(true);
|
|
56
63
|
}, [position, gap]);
|
|
57
|
-
|
|
64
|
+
|
|
65
|
+
const hide = useCallback(() => {
|
|
66
|
+
setIsVisible(false);
|
|
67
|
+
if (
|
|
68
|
+
typeof document !== "undefined" &&
|
|
69
|
+
containerRef.current &&
|
|
70
|
+
containerRef.current.contains(document.activeElement)
|
|
71
|
+
) {
|
|
72
|
+
document.activeElement.blur();
|
|
73
|
+
}
|
|
74
|
+
}, []);
|
|
75
|
+
|
|
76
|
+
useEffect(() => {
|
|
77
|
+
if (isVisible) {
|
|
78
|
+
window.addEventListener("scroll", hide, { passive: true });
|
|
79
|
+
return () => window.removeEventListener("scroll", hide);
|
|
80
|
+
}
|
|
81
|
+
}, [isVisible, hide]);
|
|
82
|
+
|
|
83
|
+
useEffect(() => {
|
|
84
|
+
if (
|
|
85
|
+
isVisible &&
|
|
86
|
+
tooltipRef.current &&
|
|
87
|
+
(position === "top" || position === "bottom")
|
|
88
|
+
) {
|
|
89
|
+
const rect = tooltipRef.current.getBoundingClientRect();
|
|
90
|
+
const padding = 12;
|
|
91
|
+
let newLeft = coords.originalLeft;
|
|
92
|
+
const halfWidth = rect.width / 2;
|
|
93
|
+
|
|
94
|
+
if (coords.originalLeft - halfWidth < padding) {
|
|
95
|
+
newLeft = halfWidth + padding;
|
|
96
|
+
} else if (
|
|
97
|
+
coords.originalLeft + halfWidth >
|
|
98
|
+
window.innerWidth - padding
|
|
99
|
+
) {
|
|
100
|
+
newLeft = window.innerWidth - halfWidth - padding;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (newLeft !== coords.left) {
|
|
104
|
+
setCoords((prev) => ({ ...prev, left: newLeft }));
|
|
105
|
+
setArrowOffset(newLeft - coords.originalLeft);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}, [isVisible, coords.originalLeft, coords.left, position]);
|
|
109
|
+
|
|
58
110
|
const tooltip =
|
|
59
111
|
isVisible && typeof document !== "undefined"
|
|
60
112
|
? createPortal(
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
{
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
this,
|
|
76
|
-
),
|
|
77
|
-
],
|
|
78
|
-
},
|
|
79
|
-
undefined,
|
|
80
|
-
true,
|
|
81
|
-
undefined,
|
|
82
|
-
this,
|
|
83
|
-
),
|
|
113
|
+
<span
|
|
114
|
+
ref={tooltipRef}
|
|
115
|
+
className={`${styles.tooltip} ${styles[position]}`}
|
|
116
|
+
style={{
|
|
117
|
+
...tooltipStyle,
|
|
118
|
+
top: coords.top,
|
|
119
|
+
left: coords.left,
|
|
120
|
+
"--arrow-offset": `${arrowOffset}px`,
|
|
121
|
+
}}
|
|
122
|
+
role="tooltip"
|
|
123
|
+
>
|
|
124
|
+
{msg}
|
|
125
|
+
<span className={styles.arrow} />
|
|
126
|
+
</span>,
|
|
84
127
|
document.body,
|
|
85
128
|
)
|
|
86
129
|
: null;
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
ref
|
|
91
|
-
className
|
|
92
|
-
onMouseEnter
|
|
93
|
-
onMouseLeave
|
|
94
|
-
onFocus
|
|
95
|
-
onBlur
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
this,
|
|
130
|
+
|
|
131
|
+
return (
|
|
132
|
+
<div
|
|
133
|
+
ref={containerRef}
|
|
134
|
+
className={`${styles.tooltipContainer} ${underline ? styles.hasUnderline : ""} ${className} ${isVisible ? styles.isActive : ""}`}
|
|
135
|
+
onMouseEnter={show}
|
|
136
|
+
onMouseLeave={hide}
|
|
137
|
+
onFocus={show}
|
|
138
|
+
onBlur={hide}
|
|
139
|
+
onClick={() => (isVisible ? hide() : show())}
|
|
140
|
+
style={{ display: "contents" }}
|
|
141
|
+
>
|
|
142
|
+
{children}
|
|
143
|
+
{tooltip}
|
|
144
|
+
</div>
|
|
103
145
|
);
|
|
104
146
|
}
|
|
@@ -21,7 +21,17 @@
|
|
|
21
21
|
transition: background-image 0.2s ease;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
@media (hover: hover) {
|
|
25
|
+
.hasUnderline:hover {
|
|
26
|
+
background-image: radial-gradient(
|
|
27
|
+
circle,
|
|
28
|
+
color-mix(in srgb, var(--ifm-color-primary-darker), transparent 10%) 1.2px,
|
|
29
|
+
transparent 1.5px
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.hasUnderline.isActive {
|
|
25
35
|
background-image: radial-gradient(
|
|
26
36
|
circle,
|
|
27
37
|
color-mix(in srgb, var(--ifm-color-primary-darker), transparent 10%) 1.2px,
|
|
@@ -83,7 +93,7 @@
|
|
|
83
93
|
|
|
84
94
|
.top .arrow {
|
|
85
95
|
top: 100%;
|
|
86
|
-
left: 50
|
|
96
|
+
left: calc(50% - var(--arrow-offset, 0px));
|
|
87
97
|
transform: translateX(-50%) rotate(45deg);
|
|
88
98
|
margin-top: -5.5px;
|
|
89
99
|
border-right: 1px solid var(--ifm-color-emphasis-300);
|
|
@@ -92,7 +102,7 @@
|
|
|
92
102
|
|
|
93
103
|
.bottom .arrow {
|
|
94
104
|
bottom: 100%;
|
|
95
|
-
left: 50
|
|
105
|
+
left: calc(50% - var(--arrow-offset, 0px));
|
|
96
106
|
transform: translateX(-50%) rotate(45deg);
|
|
97
107
|
margin-bottom: -5.5px;
|
|
98
108
|
border-left: 1px solid var(--ifm-color-emphasis-300);
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import DocCardList from "@theme/DocCardList";
|
|
2
|
+
import { useCurrentSidebarCategory } from "@docusaurus/plugin-content-docs/client";
|
|
3
|
+
|
|
4
|
+
// List Topics inside Individual Notes
|
|
5
|
+
export default function TopicList({
|
|
6
|
+
children,
|
|
7
|
+
description = "Click on the links below to explore the topics.",
|
|
8
|
+
style = {
|
|
9
|
+
marginTop: "-2.5rem",
|
|
10
|
+
marginBottom: "2.5rem",
|
|
11
|
+
textAlign: "center",
|
|
12
|
+
},
|
|
13
|
+
}) {
|
|
14
|
+
let items;
|
|
15
|
+
try {
|
|
16
|
+
const category = useCurrentSidebarCategory();
|
|
17
|
+
items = category.items;
|
|
18
|
+
} catch (e) {
|
|
19
|
+
// Fallback if not on a category page
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<>
|
|
24
|
+
<br />
|
|
25
|
+
{(children || description) && (
|
|
26
|
+
<p style={style}>{children || description}</p>
|
|
27
|
+
)}
|
|
28
|
+
<DocCardList items={items} />
|
|
29
|
+
</>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/* === Global Entrance Animations === */
|
|
2
|
+
|
|
3
|
+
@keyframes fadeIn {
|
|
4
|
+
from {
|
|
5
|
+
opacity: 0;
|
|
6
|
+
}
|
|
7
|
+
to {
|
|
8
|
+
opacity: 1;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
@keyframes slideUp {
|
|
13
|
+
from {
|
|
14
|
+
opacity: 0;
|
|
15
|
+
transform: translateY(30px);
|
|
16
|
+
}
|
|
17
|
+
to {
|
|
18
|
+
opacity: 1;
|
|
19
|
+
transform: translateY(0);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
@keyframes slideInLeft {
|
|
24
|
+
from {
|
|
25
|
+
opacity: 0;
|
|
26
|
+
transform: translateX(-40px);
|
|
27
|
+
}
|
|
28
|
+
to {
|
|
29
|
+
opacity: 1;
|
|
30
|
+
transform: translateX(0);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
@keyframes slideInRight {
|
|
35
|
+
from {
|
|
36
|
+
opacity: 0;
|
|
37
|
+
transform: translateX(40px);
|
|
38
|
+
}
|
|
39
|
+
to {
|
|
40
|
+
opacity: 1;
|
|
41
|
+
transform: translateX(0);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
@keyframes scaleIn {
|
|
46
|
+
from {
|
|
47
|
+
opacity: 0;
|
|
48
|
+
transform: scale(0.95);
|
|
49
|
+
}
|
|
50
|
+
to {
|
|
51
|
+
opacity: 1;
|
|
52
|
+
transform: scale(1);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
@keyframes skillAppear {
|
|
57
|
+
to {
|
|
58
|
+
opacity: 1;
|
|
59
|
+
transform: translateY(0);
|
|
60
|
+
}
|
|
61
|
+
}
|