Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 5 additions & 12 deletions apps/app/src/components/plugin/management/BrowsePluginsTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import {
sortPluginEntries,
type PluginBrowseShelf,
} from "./plugin-browse-discovery";
import { pluginCatalogCategoryMutedAccentStyle } from "./plugin-ui";
import { PluginCategoryIcon } from "./plugin-ui";

const SHELF_ENTRY_LIMIT = 6;

Expand Down Expand Up @@ -215,12 +215,9 @@ export function BrowsePluginsTab({
<h1 className="flex flex-wrap items-center gap-2 text-xl font-semibold text-foreground">
<span className="inline-flex min-w-0 items-center gap-2">
{selectedShelf.key.startsWith("category:") ? (
<span
className="size-2 shrink-0 rounded-full"
style={pluginCatalogCategoryMutedAccentStyle(
selectedShelf.categoryId,
)}
aria-hidden
<PluginCategoryIcon
categoryId={selectedShelf.categoryId}
className="size-5"
/>
) : null}
{selectedShelf.label}
Expand Down Expand Up @@ -362,11 +359,7 @@ function BrowseShelf({
) : shelf.key === "collection:new-and-notable" ? (
<Icon name="News01" className="size-4 text-foreground" aria-hidden />
) : (
<span
className="size-2 rounded-full"
style={pluginCatalogCategoryMutedAccentStyle(shelf.categoryId)}
aria-hidden
/>
<PluginCategoryIcon categoryId={shelf.categoryId} className="size-4" />
)
}
browseAction={
Expand Down
48 changes: 33 additions & 15 deletions apps/app/src/components/plugin/management/PluginCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,27 +81,45 @@ interface PluginCardAuthorProps {
>;
}

function pluginCardAuthorName(entry: PluginCardAuthorProps["entry"]): string {
return entry.marketplace === "bb-official"
? "BB Official"
: (entry.author?.name ?? entry.publisherLabel);
}

export function PluginCardAuthorAvatar({ entry }: PluginCardAuthorProps) {
return (
<PluginAuthorAvatar
name={pluginCardAuthorName(entry)}
github={pluginAuthorGithub(entry.author)}
official={entry.marketplace === "bb-official"}
size="detail"
/>
);
}

export function PluginCardAuthorName({ entry }: PluginCardAuthorProps) {
const name = pluginCardAuthorName(entry);
return entry.author === null ? (
name
) : (
<PluginAuthorLink
entry={entry}
className="pointer-events-auto relative z-10 rounded-sm underline-offset-2 hover:text-foreground hover:underline focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
>
{name}
</PluginAuthorLink>
);
}

export function PluginCardAuthor({ entry }: PluginCardAuthorProps) {
const name =
entry.marketplace === "bb-official"
? "BB Official"
: (entry.author?.name ?? entry.publisherLabel);
return (
<PluginAuthorByline
name={name}
name={pluginCardAuthorName(entry)}
github={pluginAuthorGithub(entry.author)}
official={entry.marketplace === "bb-official"}
>
{entry.author === null ? (
name
) : (
<PluginAuthorLink
entry={entry}
className="pointer-events-auto relative z-10 rounded-sm underline-offset-2 hover:text-foreground hover:underline focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
>
{name}
</PluginAuthorLink>
)}
<PluginCardAuthorName entry={entry} />
</PluginAuthorByline>
);
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useEffect, useMemo, useState, type ReactNode } from "react";
import { Link } from "react-router-dom";
import {
Carousel,
CarouselContent,
Expand All @@ -17,23 +18,51 @@ import {
} from "@bb/shared-ui/resource-list";
import type { PluginCatalogSearchEntry } from "@/hooks/queries/plugin-catalog-queries";
import { PluginOverviewMarkdown } from "@/components/plugin/management/PluginOverviewMarkdown";
import { getPluginsRoutePath } from "@/lib/route-paths";
import { PluginCardAuthorName } from "./PluginCard";
import {
CatalogEntryIconChip,
formatUrlLabel,
PluginCategoryLabel,
pluginCatalogCategoryIconName,
PluginCategoryIcon,
} from "./plugin-ui";
import {
entriesByMarketplaceAuthor,
pluginMarketplaceAuthorKey,
} from "./plugin-marketplace-author";

export function PluginMarketplaceCategoryPill({
export function PluginMarketplaceByline({
entry,
}: {
entry: PluginCatalogSearchEntry;
entry: Pick<
PluginCatalogSearchEntry,
"author" | "marketplace" | "publisherLabel" | "category" | "categoryId"
>;
}) {
return entry.category === undefined ? null : (
<PluginCategoryLabel categoryId={entry.categoryId} label={entry.category} />
return (
<span className="flex min-w-0 items-center gap-2">
<span className="min-w-0 truncate">
<PluginCardAuthorName entry={entry} />
</span>
{entry.category === undefined ||
pluginCatalogCategoryIconName(entry.categoryId) === undefined ? null : (
<span className="flex min-w-0 shrink-[100] items-center gap-1">
<PluginCategoryIcon categoryId={entry.categoryId} className="size-3" />
<Link
to={{
pathname: getPluginsRoutePath(),
search: new URLSearchParams({
shelf: `category:${entry.categoryId}`,
}).toString(),
}}
aria-label={`Browse ${entry.category} plugins`}
className="min-w-0 truncate rounded-sm underline decoration-border underline-offset-2 hover:text-foreground hover:decoration-current focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
>
{entry.category}
</Link>
</span>
)}
</span>
);
}

Expand Down Expand Up @@ -63,9 +92,11 @@ export function PluginMarketplaceDetailMetadata({
}) {
return (
<>
<PluginDetailMetadataItem label="Marketplace">
{entry.marketplaceDisplayName}
</PluginDetailMetadataItem>
{entry.marketplace === "bb-official" ? null : (
<PluginDetailMetadataItem label="Marketplace">
{entry.marketplaceDisplayName}
</PluginDetailMetadataItem>
)}
{entry.publishedAt === undefined ? null : (
<PluginDetailMetadataItem label="Listed">
<time dateTime={entry.publishedAt}>
Expand Down Expand Up @@ -193,7 +224,7 @@ function PluginScreenshotGallery({
export function PluginOverviewLead({ description }: { description: string }) {
return (
<p
className="text-base leading-relaxed text-foreground"
className="text-sm leading-relaxed text-foreground"
data-plugin-summary=""
>
{description}
Expand All @@ -209,7 +240,7 @@ export function PluginMarketplaceOverview({
return (
<section className="space-y-6" data-resource-detail-section="overview">
<PluginScreenshotGallery entry={entry} />
<div className="space-y-3">
<div className="max-w-prose space-y-4">
<PluginOverviewLead description={entry.description} />
{entry.overview === undefined ? null : (
<>
Expand All @@ -232,11 +263,14 @@ export function PluginMarketplaceListingSections({
<>
<PluginMarketplaceOverview entry={entry} />
<PluginMarketplaceSource entry={entry} />
<ResourceDefinitionSection label="Details">
<PluginDetailMetadata>
<PluginMarketplaceDetailMetadata entry={entry} />
</PluginDetailMetadata>
</ResourceDefinitionSection>
{entry.marketplace === "bb-official" &&
entry.publishedAt === undefined ? null : (
<ResourceDefinitionSection label="Details">
<PluginDetailMetadata>
<PluginMarketplaceDetailMetadata entry={entry} />
</PluginDetailMetadata>
</ResourceDefinitionSection>
)}
</>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,13 @@ function OverviewHeading({
}) {
const Tag = minor ? "h4" : "h3";
return (
<Tag className="mb-1.5 mt-5 text-xs font-semibold uppercase tracking-wide text-subtle-foreground first:mt-0">
<Tag
className={
minor
? "mb-1 mt-4 text-xs font-medium text-foreground first:mt-0"
: "mb-1 mt-6 text-sm font-medium text-foreground first:mt-0"
}
>
{children}
</Tag>
);
Expand All @@ -77,7 +83,7 @@ const OVERVIEW_COMPONENTS: Components = {
</blockquote>
),
code: ({ children }) => (
<code className="rounded bg-surface-recessed px-1 py-0.5 font-mono text-xs text-foreground">
<code className="rounded-sm bg-surface-recessed px-1 font-mono text-xs text-foreground [box-decoration-break:clone]">
{children}
</code>
),
Expand All @@ -88,15 +94,23 @@ const OVERVIEW_COMPONENTS: Components = {
h5: ({ children }) => <OverviewHeading minor>{children}</OverviewHeading>,
h6: ({ children }) => <OverviewHeading minor>{children}</OverviewHeading>,
hr: () => <hr className="my-4 border-t border-border" />,
li: ({ children }) => <li className="mb-1">{children}</li>,
ol: ({ children }) => <ol className="mb-2 list-decimal pl-5">{children}</ol>,
p: ({ children }) => <p className="mb-2 last:mb-0">{children}</p>,
li: ({ children }) => <li className="pl-0.5">{children}</li>,
ol: ({ children }) => (
<ol className="mb-3 list-decimal space-y-1 pl-4 marker:text-subtle-foreground">
{children}
</ol>
),
p: ({ children }) => <p className="mb-3 last:mb-0">{children}</p>,
pre: ({ children }) => (
<pre className="my-2 overflow-x-auto rounded-md border border-border bg-surface-recessed p-3 font-mono text-xs text-foreground">
{children}
</pre>
),
ul: ({ children }) => <ul className="mb-2 list-disc pl-5">{children}</ul>,
ul: ({ children }) => (
<ul className="mb-3 list-disc space-y-1 pl-4 marker:text-subtle-foreground">
{children}
</ul>
),
};

export function PluginOverviewMarkdown({ markdown }: { markdown: string }) {
Expand Down
18 changes: 7 additions & 11 deletions apps/app/src/components/plugin/management/plugin-ui.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { PLUGIN_CATALOG_CATEGORIES } from "@bb/domain";
import {
CatalogEntryIcon,
CatalogEntryIconChip,
pluginCatalogCategoryPillStyle,
pluginCatalogCategoryIconName,
pluginInstallCountPresentation,
} from "./plugin-ui";

Expand Down Expand Up @@ -92,17 +92,13 @@ it("uses one glyph box for host and marketplace catalog icons", () => {
}
});

it("uses theme accents for all built-in categories and neutral unknowns", () => {
for (const category of PLUGIN_CATALOG_CATEGORIES) {
const style = pluginCatalogCategoryPillStyle(category.id);
expect(String(style.background)).toContain("color-mix(in oklab");
expect(String(style.background)).toContain("var(--");
expect(String(style.background)).not.toContain("var(--ink) 8%");
}
const unknown = pluginCatalogCategoryPillStyle("future-category");
expect(unknown.background).toBe(
"color-mix(in oklch, var(--ink) 8%, var(--canvas))",
it("gives every built-in category its own icon and unknowns none", () => {
const icons = PLUGIN_CATALOG_CATEGORIES.map((category) =>
pluginCatalogCategoryIconName(category.id),
);
expect(icons).not.toContain(undefined);
expect(new Set(icons).size).toBe(icons.length);
expect(pluginCatalogCategoryIconName("future-category")).toBeUndefined();
});

it("labels install counts only when the catalog knows them", () => {
Expand Down
Loading
Loading