Link Recordly social accounts from a labeled Home footer

Use the verified Recordly X and YouTube accounts alongside Discord and GitHub. Give Upgrade a full row and use a labeled two-column grid with keyboard access and consistent targets.
This commit is contained in:
webadderall committed 2026-11-06 20:26:58 +11:00
1 parent 44748890e3
commit fad617f381
2 files changed
+117 -72

No files matched your search

+42 -42
View File
@@ -1,4 +1,4 @@
import { GithubLogo, XLogo } from "@phosphor-icons/react";
import { GithubLogo, XLogo, YoutubeLogo } from "@phosphor-icons/react";
import { useEffect, useState } from "react";
import { UpgradeButton } from "@/components/billing/UpgradeButton";
import { Button } from "@/components/ui/button";
@@ -26,7 +26,8 @@ import { formatShortcut } from "@/utils/platformUtils";
const RECORDLY_GITHUB_URL = "https://github.com/webadderallorg/Recordly";
export const RECORDLY_ISSUES_URL = `${RECORDLY_GITHUB_URL}/issues`;
const RECORDLY_DISCORD_URL = "https://discord.gg/sdv2FBVNgE";
const RECORDLY_X_URL = "https://x.com/webadderall";
const RECORDLY_X_URL = "https://x.com/RecordlyHQ";
const RECORDLY_YOUTUBE_URL = "https://www.youtube.com/@recordlydev";
export const APP_HEADER_ACTION_BUTTON_CLASS =
"h-7 px-2 text-xs text-muted-foreground hover:bg-foreground/10 hover:text-foreground transition-all gap-1.5";
export const APP_HEADER_ICON_BUTTON_CLASS =
@@ -84,53 +85,52 @@ export function DiscordLinkButton({ className }: { className?: string }) {
export function CommunityLinks() {
const t = useScopedT("editor");
const tBilling = useScopedT("billing");
const buttonClassName =
"size-7 min-w-7 rounded-lg p-0 text-muted hover:bg-foreground/5 hover:text-foreground";
const tEditing = useScopedT("editing");
const links = [
{
label: "Discord",
accessibleLabel: tEditing("joinDiscord", "Join Discord"),
url: RECORDLY_DISCORD_URL,
Icon: DiscordIcon,
},
{ label: "GitHub", accessibleLabel: "GitHub", url: RECORDLY_GITHUB_URL, Icon: GithubLogo },
{ label: "X", accessibleLabel: "X", url: RECORDLY_X_URL, Icon: XLogo },
{
label: "YouTube",
accessibleLabel: "YouTube",
url: RECORDLY_YOUTUBE_URL,
Icon: YoutubeLogo,
},
];
return (
<div className="community-links mt-2 flex shrink-0 items-center gap-1">
<div className="community-links mt-2 flex shrink-0 flex-col gap-2">
<UpgradeButton
label={tBilling("upgradeShort", "Upgrade")}
showDiscount={false}
truncateLabel
className="h-8 min-w-0 flex-1 px-1.5 text-xs"
className="h-8 w-full min-w-0 px-1.5 text-xs"
/>
<div className="flex shrink-0 items-center gap-1">
<DiscordLinkButton className={buttonClassName} />
<Button
type="button"
variant="ghost"
size="sm"
isIconOnly
className={buttonClassName}
title="GitHub"
aria-label="GitHub"
onClick={() =>
void openExternalLink(
RECORDLY_GITHUB_URL,
t("feedback.openFailed", "Failed to open link."),
)
}
>
<GithubLogo className="size-4" aria-hidden="true" />
</Button>
<Button
type="button"
variant="ghost"
size="sm"
isIconOnly
className={buttonClassName}
title="X"
aria-label="X"
onClick={() =>
void openExternalLink(
RECORDLY_X_URL,
t("feedback.openFailed", "Failed to open link."),
)
}
>
<XLogo className="size-4" aria-hidden="true" />
</Button>
<div role="group" aria-label="Recordly" className="grid grid-cols-2 gap-1">
{links.map(({ label, accessibleLabel, url, Icon }) => (
<Button
key={label}
type="button"
variant="ghost"
size="sm"
className="h-8 w-full min-w-0 justify-start gap-1.5 rounded-lg px-1.5 text-[11px] text-muted-foreground hover:bg-foreground/5 hover:text-foreground"
aria-label={accessibleLabel}
onClick={() =>
void openExternalLink(
url,
t("feedback.openFailed", "Failed to open link."),
)
}
>
<Icon className="size-3.5 shrink-0" aria-hidden="true" />
<span>{label}</span>
</Button>
))}
</div>
</div>
);
+75 -30
View File
@@ -144,32 +144,57 @@ for (const height of [600, 760]) {
});
}
test("Home community footer opens X beside Discord and GitHub", async ({ page }) => {
test.setTimeout(60000);
await page.setViewportSize({ width: 1000, height: 600 });
await page.emulateMedia({ reducedMotion: "reduce" });
await installDesktopBridge(page);
await installDesktopBridgeOverrides(page, () => {
window.electronAPI.openExternalUrl = async (url) => {
document.documentElement.dataset.openedCommunityUrl = url;
return { success: true };
};
for (const { width, height, theme } of [
{ width: 1000, height: 600, theme: "dark" },
{ width: 1280, height: 760, theme: "light" },
] as const) {
test(`Home community links are labeled and open Recordly accounts (${theme}, ${width}×${height})`, async ({
page,
}) => {
test.setTimeout(60000);
await page.setViewportSize({ width, height });
await page.emulateMedia({ reducedMotion: "reduce", colorScheme: theme });
await installDesktopBridge(page);
await page.addInitScript((value) => localStorage.setItem("recordly.theme", value), theme);
await installDesktopBridgeOverrides(page, () => {
window.electronAPI.openExternalUrl = async (url) => {
document.documentElement.dataset.openedCommunityUrl = url;
return { success: true };
};
});
await page.goto("/?windowType=editor");
await page.getByRole("button", { name: "Home", exact: true }).click();
const home = page.getByRole("dialog", { name: "Projects dashboard", exact: true });
const links = home.getByRole("group", { name: "Recordly", exact: true });
await expect(links.getByRole("button")).toHaveCount(4);
const url = page.url();
for (const [name, label, target] of [
["Join Discord", "Discord", "https://discord.gg/sdv2FBVNgE"],
["GitHub", "GitHub", "https://github.com/webadderallorg/Recordly"],
["X", "X", "https://x.com/RecordlyHQ"],
["YouTube", "YouTube", "https://www.youtube.com/@recordlydev"],
]) {
const button = links.getByRole("button", { name, exact: true });
await expect(button).toHaveText(label);
await expect(button).toBeInViewport({ ratio: 1 });
if (name === "YouTube") {
await button.focus();
await page.keyboard.press("Enter");
} else {
await button.click();
}
await expect(page.locator("html")).toHaveAttribute("data-opened-community-url", target);
await expect(page).toHaveURL(url);
}
await home
.locator("aside")
.screenshot({ path: test.info().outputPath(`community-${theme}.png`) });
});
await page.goto("/?windowType=editor");
await page.getByRole("button", { name: "Home", exact: true }).click();
const home = page.getByRole("dialog", { name: "Projects dashboard", exact: true });
const x = home.getByRole("button", { name: "X", exact: true });
await expect(home.getByRole("button", { name: "Join Discord", exact: true })).toBeVisible();
await expect(home.getByRole("button", { name: "GitHub", exact: true })).toBeVisible();
await expect(x).toBeVisible();
expect(await x.evaluate((button) => Array.from(button.parentElement!.querySelectorAll("button"), (item) => item.getAttribute("aria-label")))).toEqual(["Join Discord", "GitHub", "X"]);
const url = page.url();
await x.click();
await expect(page.locator("html")).toHaveAttribute("data-opened-community-url", "https://x.com/webadderall");
await expect(page).toHaveURL(url);
});
}
test("Japanese community footer keeps its full Upgrade label accessible without overlapping social icons", async ({ page }) => {
test("Japanese community footer fits Upgrade and labeled links without overlap", async ({
page,
}) => {
test.setTimeout(60000);
await page.setViewportSize({ width: 1000, height: 600 });
await page.emulateMedia({ reducedMotion: "reduce" });
@@ -185,20 +210,40 @@ test("Japanese community footer keeps its full Upgrade label accessible without
await expect(label).toHaveText("アップグレード");
await expect(label).toHaveCSS("text-overflow", "ellipsis");
await expect(label).toHaveCSS("overflow-x", "hidden");
// Exercise a label wider than this sidebar's available button space; its
// full text remains available to assistive technology and the title tooltip.
expect(await label.evaluate((element) => element.scrollWidth > element.clientWidth)).toBe(true);
// Upgrade has its own row so even the Japanese label fits at the narrow sidebar width.
expect(await label.evaluate((element) => element.scrollWidth <= element.clientWidth)).toBe(
true,
);
await expect(label).toHaveAttribute("title", "アップグレード");
const bounds = (await footer.boundingBox())!;
let previousRight = bounds.x;
const boxes = [];
for (const button of await footer.getByRole("button").all()) {
await expect(button).toBeInViewport({ ratio: 1 });
const box = (await button.boundingBox())!;
expect(box.x).toBeGreaterThanOrEqual(previousRight);
expect(box.x).toBeGreaterThanOrEqual(bounds.x);
expect(box.x + box.width).toBeLessThanOrEqual(bounds.x + bounds.width);
previousRight = box.x + box.width;
expect(box.height).toBeGreaterThanOrEqual(32);
for (const previous of boxes) {
expect(
box.x >= previous.x + previous.width ||
box.x + box.width <= previous.x ||
box.y >= previous.y + previous.height ||
box.y + box.height <= previous.y,
).toBe(true);
}
boxes.push(box);
const text = button.locator("span").first();
expect(await text.evaluate((element) => element.scrollWidth <= element.clientWidth)).toBe(
true,
);
}
const socialBounds = (await footer
.getByRole("group", { name: "Recordly", exact: true })
.boundingBox())!;
const upgradeBounds = (await upgrade.boundingBox())!;
expect(socialBounds.y).toBeGreaterThanOrEqual(upgradeBounds.y + upgradeBounds.height);
await expect(footer.getByRole("button", { name: "GitHub", exact: true })).toBeVisible();
await expect(footer.getByRole("button", { name: "X", exact: true })).toBeVisible();
await expect(footer.getByRole("button", { name: "YouTube", exact: true })).toBeVisible();
await footer.screenshot({ path: test.info().outputPath("community-ja.png") });
});