diff --git a/src/components/CardWithIcon/CardWithIcon.tsx b/src/components/CardWithIcon/CardWithIcon.tsx
index 642612d3a8..fee9900030 100644
--- a/src/components/CardWithIcon/CardWithIcon.tsx
+++ b/src/components/CardWithIcon/CardWithIcon.tsx
@@ -1,12 +1,18 @@
import Link from '@docusaurus/Link';
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
+import styled from 'styled-components';
import { theme, VerticalTile } from '@apify/ui-library';
+import { cardHoverStyles } from '../cardHoverStyles';
import { Heading } from '../Heading';
import { Text } from '../Text';
import styles from './styles.module.css';
+const StyledVerticalTile = styled(VerticalTile)`
+ ${cardHoverStyles}
+`;
+
interface CardWithIconProps {
icon: React.ReactNode;
title: string;
@@ -20,7 +26,7 @@ export default function CardWithIcon({ icon, title, description, to, width }: Ca
const external = to.startsWith('http');
const Tile = (
-
diff --git a/src/components/Cards.module.css b/src/components/Cards.module.css
index 312601b9e5..e9cbf70faa 100644
--- a/src/components/Cards.module.css
+++ b/src/components/Cards.module.css
@@ -3,12 +3,13 @@
border-radius: 0.5rem;
}
-.card-hoverable:hover {
- background-color: rgb(241, 242, 247);
+.card.card-hoverable:hover {
+ background-color: var(--color-neutral-card-background-hover);
}
-html[data-theme='dark'] .card-hoverable:hover {
- background-color: rgb(30, 40, 59);
+html[data-theme='dark'] .card {
+ background: var(--color-neutral-card-background);
+ border-color: var(--color-neutral-border);
}
.card-image-container {
@@ -40,6 +41,10 @@ html[data-theme='dark'] .card-hoverable:hover {
box-sizing: border-box;
}
+html[data-theme='dark'] .card-header {
+ border-bottom-color: var(--color-neutral-border);
+}
+
.card-links {
list-style-type: none;
padding: 0 0.5rem 1rem 1rem;
diff --git a/src/components/GitButton.tsx b/src/components/GitButton.tsx
index dc83c1897b..c30f780255 100644
--- a/src/components/GitButton.tsx
+++ b/src/components/GitButton.tsx
@@ -1,9 +1,103 @@
-import { useColorMode } from '@docusaurus/theme-common';
-import type { GitHubButtonProps } from 'github-buttons';
-import type { PropsWithChildren } from 'react';
-import GitHubButton from 'react-github-btn';
-
-export default function GitButton(props: PropsWithChildren) {
- const { colorMode } = useColorMode();
- return ;
+import { useEffect, useState } from 'react';
+import styled from 'styled-components';
+
+import { theme } from '@apify/ui-library';
+
+type GitButtonProps = {
+ href: string;
+ ariaLabel: string;
+};
+
+const GIT_BUTTON_CLASSNAMES = {
+ LABEL: 'GitButton__label',
+ COUNT: 'GitButton__count',
+} as const;
+
+// Deduplicates star-count fetches per repo within a session. Unauthenticated GitHub API
+// allows 60 req/hr per visitor IP - plenty for a handful of buttons, and on failure the
+// count is simply not shown.
+const starCache = new Map>();
+
+function fetchStars(repo: string): Promise {
+ if (!starCache.has(repo)) {
+ starCache.set(
+ repo,
+ fetch(`https://api.github.com/repos/${repo}`)
+ .then(async (response) => (response.ok ? response.json() : null))
+ .then((data) => (typeof data?.stargazers_count === 'number' ? data.stargazers_count : null))
+ .catch(() => null),
+ );
+ }
+ return starCache.get(repo)!;
}
+
+const GitHubMarkIcon = () => (
+
+);
+
+const StyledGitButton = styled.a`
+ display: inline-flex;
+ align-items: stretch;
+ /* page-level rules (e.g. .Description a { display: flex }) can stretch the anchor */
+ width: fit-content;
+ height: 30px;
+ border: 1px solid ${theme.color.neutral.border};
+ border-radius: ${theme.radius.radius6};
+ background-color: ${theme.color.neutral.cardBackground};
+ color: ${theme.color.neutral.text};
+ ${theme.typography.shared.desktop.bodyMStrong}
+ overflow: hidden;
+ transition: background-color ${theme.transition.fastEaseOut};
+
+ &:hover {
+ text-decoration: none;
+ color: ${theme.color.neutral.text};
+ background-color: ${theme.color.neutral.hover};
+ }
+
+ .${GIT_BUTTON_CLASSNAMES.LABEL} {
+ display: inline-flex;
+ align-items: center;
+ gap: ${theme.space.space8};
+ padding: 0 ${theme.space.space12};
+ }
+
+ .${GIT_BUTTON_CLASSNAMES.COUNT} {
+ display: inline-flex;
+ align-items: center;
+ padding: 0 ${theme.space.space12};
+ border-left: 1px solid ${theme.color.neutral.border};
+ }
+`;
+
+// Self-rendered instead of GitHub's `buttons.github.io` iframe, which proved unreliable
+// (rendered as a white box on dark backgrounds) and had a fixed width that never matched
+// the actual button size. Same approach as `CrawleeLearnMore` in apify-web.
+const GitButton = ({ href, ariaLabel }: GitButtonProps) => {
+ const [stars, setStars] = useState(null);
+ const repo = new URL(href).pathname.replace(/^\/|\/$/g, '');
+
+ useEffect(() => {
+ let cancelled = false;
+ void fetchStars(repo).then((count) => {
+ if (!cancelled) setStars(count);
+ });
+ return () => {
+ cancelled = true;
+ };
+ }, [repo]);
+
+ return (
+
+
+
+ Star
+
+ {stars !== null && {stars.toLocaleString('en-US')}}
+
+ );
+};
+
+export default GitButton;
diff --git a/src/components/OpenSourceCards/OpenSourceCards.tsx b/src/components/OpenSourceCards/OpenSourceCards.tsx
index 30cc47cf8f..7714daf9f1 100644
--- a/src/components/OpenSourceCards/OpenSourceCards.tsx
+++ b/src/components/OpenSourceCards/OpenSourceCards.tsx
@@ -1,12 +1,11 @@
import Link from '@docusaurus/Link';
-import { useColorMode } from '@docusaurus/theme-common';
import useBaseUrl from '@docusaurus/useBaseUrl';
import type React from 'react';
-import GitHubButton from 'react-github-btn';
import { theme } from '@apify/ui-library';
import CardWithImageAndContent from '../CardWithImageAndContent/ImageWithContent';
+import GitButton from '../GitButton';
import { Heading } from '../Heading';
import { Text } from '../Text';
import styles from './styles.module.css';
@@ -16,8 +15,6 @@ interface OpenSourceCardsProps {
}
const OpenSourceCards: React.FC = ({ hideCrawlee = false }) => {
- const { colorMode } = useColorMode();
-
return (
<>
{!hideCrawlee && (
@@ -41,15 +38,10 @@ const OpenSourceCards: React.FC = ({ hideCrawlee = false }