@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.
Files changed (49) hide show
  1. package/package.json +12 -3
  2. package/src/plugins/theme.mjs +2 -0
  3. package/theme/DocCategoryGeneratedIndexPage/index.jsx +4 -10
  4. package/theme/MDXComponents.jsx +3 -2
  5. package/theme/Root.jsx +1 -1
  6. package/theme/components/AboutSection/index.jsx +89 -249
  7. package/theme/components/ContactSection/index.jsx +72 -153
  8. package/theme/components/ExperienceSection/index.jsx +35 -106
  9. package/theme/components/HeroSection/index.jsx +64 -186
  10. package/theme/components/Indent/index.jsx +10 -0
  11. package/theme/components/NavArrow/index.jsx +38 -55
  12. package/theme/components/NoteCards/index.jsx +110 -0
  13. package/theme/components/Preview/components/FeedbackStates.jsx +45 -190
  14. package/theme/components/Preview/components/FileTabs.jsx +17 -24
  15. package/theme/components/Preview/components/PreviewContent.jsx +37 -62
  16. package/theme/components/Preview/components/PreviewHeader.jsx +146 -380
  17. package/theme/components/Preview/components/Triggers/Pv.jsx +50 -78
  18. package/theme/components/Preview/components/Triggers/SrcPv.jsx +16 -47
  19. package/theme/components/Preview/components/Triggers/index.jsx +2 -2
  20. package/theme/components/Preview/components/ViewerWindow.jsx +160 -268
  21. package/theme/components/Preview/index.jsx +3 -3
  22. package/theme/components/Preview/renderers/CodeRenderer.jsx +81 -109
  23. package/theme/components/Preview/renderers/ImageRenderer.jsx +30 -67
  24. package/theme/components/Preview/renderers/PdfRenderer.jsx +31 -52
  25. package/theme/components/Preview/renderers/WebRenderer.jsx +18 -32
  26. package/theme/components/Preview/state/index.jsx +46 -30
  27. package/theme/components/ProjectsSection/index.jsx +278 -573
  28. package/theme/components/ScrollToTop/index.jsx +93 -0
  29. package/theme/components/ScrollToTop/styles.module.css +97 -0
  30. package/theme/components/SocialLinks/index.jsx +43 -55
  31. package/theme/components/Tooltip/index.jsx +85 -43
  32. package/theme/components/Tooltip/styles.module.css +13 -3
  33. package/theme/components/TopicList/index.jsx +31 -0
  34. package/theme/css/animations.css +61 -0
  35. package/theme/css/custom.css +16 -253
  36. package/theme/css/overrides/base.css +37 -0
  37. package/theme/css/overrides/footer.css +14 -0
  38. package/theme/css/overrides/markdown.css +62 -0
  39. package/theme/css/overrides/navbar.css +37 -0
  40. package/theme/css/overrides/pagination.css +64 -0
  41. package/theme/css/overrides/search.css +28 -0
  42. package/theme/css/{catppuccin.css → overrides/variables.css} +1 -1
  43. package/theme/pages/index.jsx +25 -87
  44. package/theme/pages/notes.jsx +27 -104
  45. package/theme/pages/tasks.jsx +293 -904
  46. package/theme/components/NoteIndex/index.jsx +0 -182
  47. package/theme/css/bootstrap.css +0 -5
  48. /package/theme/components/{NoteIndex → NoteCards}/styles.module.css +0 -0
  49. /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
- const allSocialLinks = customFields.socialLinks.links || [];
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) return {};
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
- return jsxDEV_7x81h0kn(
59
- "div",
60
- {
61
- className: styles.socialIcons,
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
- return jsxDEV_7x81h0kn(
69
- Tooltip,
70
- {
71
- msg: social.desc || social.name || social.icon || "Link",
72
- position: "top",
73
- bg: displayColor,
74
- underline: false,
75
- gap: 17,
76
- children: jsxDEV_7x81h0kn(
77
- "a",
78
- {
79
- href,
80
- target: "_blank",
81
- rel: "noopener noreferrer",
82
- className: styles.socialLink,
83
- style: {
84
- "--hover-color": displayColor,
85
- animationDelay: animationDelays[index] || "0s",
86
- },
87
- "aria-label": social.name || social.icon || "social link",
88
- children: jsxDEV_7x81h0kn(
89
- IconComponent,
90
- { size: 24 },
91
- undefined,
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 React, { useState, useRef, useCallback } from "react";
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
- const hide = useCallback(() => setIsVisible(false), []);
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
- jsxDEV_7x81h0kn(
62
- "span",
63
- {
64
- className: `${styles.tooltip} ${styles[position]}`,
65
- style: { ...tooltipStyle, top: coords.top, left: coords.left },
66
- role: "tooltip",
67
- children: [
68
- msg,
69
- jsxDEV_7x81h0kn(
70
- "span",
71
- { className: styles.arrow },
72
- undefined,
73
- false,
74
- undefined,
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
- return jsxDEV_7x81h0kn(
88
- "div",
89
- {
90
- ref: containerRef,
91
- className: `${styles.tooltipContainer} ${underline ? styles.hasUnderline : ""} ${className}`,
92
- onMouseEnter: show,
93
- onMouseLeave: hide,
94
- onFocus: show,
95
- onBlur: hide,
96
- style: { display: "contents" },
97
- children: [children, tooltip],
98
- },
99
- undefined,
100
- true,
101
- undefined,
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
- .hasUnderline:hover {
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
+ }