mirror of
https://github.com/webadderallorg/Recordly.git
synced 2026-11-09 23:43:35 +00:00
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:
1 parent
44748890e3
commit
fad617f381
2 files changed
+117
-72
No files matched your search
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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") });
|
||||
});
|
||||
Reference in new issue
Block a user