chore: update about page
This commit is contained in:
parent
e53b7d96e7
commit
e564c1a993
2 changed files with 117 additions and 1 deletions
|
|
@ -1,4 +1,5 @@
|
|||
import { ExternalLinkIcon } from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
import TileSpriteStrip from "@/components/Placeholder/TileSpriteStrip";
|
||||
import { TILE_SPRITES, type TileSprite } from "@/components/Placeholder/tileSprites";
|
||||
import SettingGroup from "@/components/Settings/SettingGroup";
|
||||
|
|
@ -15,6 +16,36 @@ const PRODUCT_LINKS = [
|
|||
|
||||
const PRODUCT_POINTS = ["Open. Write. Done.", "Markdown-native.", "Fully yours."];
|
||||
|
||||
const SPONSORS = [
|
||||
{
|
||||
label: "CodeRabbit",
|
||||
href: "https://coderabbit.link/usememos",
|
||||
description: "Cut code review time & bugs in half, instantly.",
|
||||
lightLogo: "https://victorious-bubble-f69a016683.media.strapiapp.com/Orange_Typemark_43bf516c9d.svg",
|
||||
darkLogo: "https://victorious-bubble-f69a016683.media.strapiapp.com/White_Typemark_79b9189d19.svg",
|
||||
},
|
||||
{
|
||||
label: "Warp",
|
||||
href: "https://go.warp.dev/memos",
|
||||
description: "The agentic development environment.",
|
||||
lightLogo: "https://raw.githubusercontent.com/warpdotdev/brand-assets/refs/heads/main/Logos/Warp-Wordmark-Black.png",
|
||||
darkLogo: "https://raw.githubusercontent.com/warpdotdev/brand-assets/refs/heads/main/Logos/Warp-Wordmark-White.png",
|
||||
},
|
||||
];
|
||||
|
||||
type Sponsor = (typeof SPONSORS)[number];
|
||||
|
||||
const isDarkThemeName = (theme: string | null): boolean => {
|
||||
return theme?.endsWith("-dark") || theme?.endsWith(".dark") || false;
|
||||
};
|
||||
|
||||
const getCurrentThemeUsesDarkLogo = (): boolean => {
|
||||
if (typeof document === "undefined") {
|
||||
return false;
|
||||
}
|
||||
return isDarkThemeName(document.documentElement.getAttribute("data-theme"));
|
||||
};
|
||||
|
||||
const BirdSprite = ({ sprite }: { sprite: TileSprite }) => {
|
||||
return (
|
||||
<figure className="flex w-auto min-w-28 flex-none flex-col items-center gap-3 rounded-xl border border-border bg-muted/20 px-4 py-4 text-center">
|
||||
|
|
@ -26,6 +57,29 @@ const BirdSprite = ({ sprite }: { sprite: TileSprite }) => {
|
|||
);
|
||||
};
|
||||
|
||||
const SponsorLogo = ({ sponsor }: { sponsor: Sponsor }) => {
|
||||
const [usesDarkLogo, setUsesDarkLogo] = useState(getCurrentThemeUsesDarkLogo);
|
||||
|
||||
useEffect(() => {
|
||||
const updateLogoTheme = () => setUsesDarkLogo(getCurrentThemeUsesDarkLogo());
|
||||
|
||||
updateLogoTheme();
|
||||
|
||||
const observer = new MutationObserver(updateLogoTheme);
|
||||
observer.observe(document.documentElement, { attributes: true, attributeFilter: ["data-theme"] });
|
||||
|
||||
return () => observer.disconnect();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<img
|
||||
className="h-9 max-w-44 object-contain object-left"
|
||||
src={usesDarkLogo ? sponsor.darkLogo : sponsor.lightLogo}
|
||||
alt={sponsor.label}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const About = () => {
|
||||
return (
|
||||
<section className="mx-auto w-full max-w-5xl min-h-full flex flex-col justify-start items-start sm:pt-3 md:pt-6 pb-8">
|
||||
|
|
@ -71,6 +125,31 @@ const About = () => {
|
|||
</div>
|
||||
</SettingGroup>
|
||||
|
||||
<SettingGroup showSeparator title="Sponsors" description="Featured sponsors helping keep Memos open-source and independent.">
|
||||
<section aria-label="Sponsors" className="grid gap-3 sm:grid-cols-2">
|
||||
{SPONSORS.map((sponsor) => (
|
||||
<a
|
||||
key={sponsor.href}
|
||||
href={sponsor.href}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="group flex min-h-32 min-w-0 flex-col justify-between gap-5 rounded-lg border border-border bg-muted/20 px-4 py-4 text-muted-foreground transition-colors hover:border-primary/40 hover:bg-muted/40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background"
|
||||
>
|
||||
<div className="flex min-w-0 items-start justify-between gap-4">
|
||||
<SponsorLogo sponsor={sponsor} />
|
||||
<ExternalLinkIcon className="mt-0.5 size-4 shrink-0 text-muted-foreground transition-colors group-hover:text-foreground" />
|
||||
</div>
|
||||
<div className="flex min-w-0 flex-col gap-3">
|
||||
<p className="text-sm leading-6 text-muted-foreground">{sponsor.description}</p>
|
||||
<span className="text-xs font-medium text-foreground transition-colors group-hover:text-primary">
|
||||
Visit {sponsor.label}
|
||||
</span>
|
||||
</div>
|
||||
</a>
|
||||
))}
|
||||
</section>
|
||||
</SettingGroup>
|
||||
|
||||
<SettingGroup showSeparator title="Birds" description="Pixel tile strips used by empty states.">
|
||||
<section aria-label="Birds" className="flex flex-row flex-wrap gap-3">
|
||||
{TILE_SPRITES.map((sprite) => (
|
||||
|
|
|
|||
|
|
@ -1,9 +1,13 @@
|
|||
import { render, screen, within } from "@testing-library/react";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { TILE_SPRITES } from "@/components/Placeholder/tileSprites";
|
||||
import About from "@/pages/About";
|
||||
|
||||
describe("<About>", () => {
|
||||
afterEach(() => {
|
||||
document.documentElement.removeAttribute("data-theme");
|
||||
});
|
||||
|
||||
it("renders the product story and current bird sprites", () => {
|
||||
render(<About />);
|
||||
|
||||
|
|
@ -11,6 +15,24 @@ describe("<About>", () => {
|
|||
expect(screen.getByText(/Capture first/i)).toBeInTheDocument();
|
||||
expect(screen.getByText(/quick capture/i)).toBeInTheDocument();
|
||||
|
||||
const sponsors = screen.getByRole("region", { name: "Sponsors" });
|
||||
expect(within(sponsors).getByRole("link", { name: /CodeRabbit/i })).toHaveAttribute("href", "https://coderabbit.link/usememos");
|
||||
expect(within(sponsors).getByRole("link", { name: /Warp/i })).toHaveAttribute("href", "https://go.warp.dev/memos");
|
||||
expect(within(sponsors).getByText(/Cut code review time/i)).toBeInTheDocument();
|
||||
expect(within(sponsors).getByText(/agentic development environment/i)).toBeInTheDocument();
|
||||
expect(within(sponsors).getByAltText("CodeRabbit").closest("a")).toHaveAttribute("href", "https://coderabbit.link/usememos");
|
||||
expect(
|
||||
within(sponsors)
|
||||
.getByText(/Cut code review time/i)
|
||||
.closest("a"),
|
||||
).toHaveAttribute("href", "https://coderabbit.link/usememos");
|
||||
expect(within(sponsors).getByAltText("Warp").closest("a")).toHaveAttribute("href", "https://go.warp.dev/memos");
|
||||
expect(
|
||||
within(sponsors)
|
||||
.getByText(/agentic development environment/i)
|
||||
.closest("a"),
|
||||
).toHaveAttribute("href", "https://go.warp.dev/memos");
|
||||
|
||||
const birds = screen.getByRole("region", { name: "Birds" });
|
||||
expect(within(birds).getAllByTestId("about-bird-sprite")).toHaveLength(TILE_SPRITES.length);
|
||||
|
||||
|
|
@ -18,4 +40,19 @@ describe("<About>", () => {
|
|||
expect(within(birds).getByText(sprite.name)).toBeInTheDocument();
|
||||
}
|
||||
});
|
||||
|
||||
it("uses dark sponsor logos when the app theme is dark", () => {
|
||||
document.documentElement.setAttribute("data-theme", "default-dark");
|
||||
|
||||
render(<About />);
|
||||
|
||||
expect(screen.getByAltText("CodeRabbit")).toHaveAttribute(
|
||||
"src",
|
||||
"https://victorious-bubble-f69a016683.media.strapiapp.com/White_Typemark_79b9189d19.svg",
|
||||
);
|
||||
expect(screen.getByAltText("Warp")).toHaveAttribute(
|
||||
"src",
|
||||
"https://raw.githubusercontent.com/warpdotdev/brand-assets/refs/heads/main/Logos/Warp-Wordmark-White.png",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue