fix(web/store): restore the plugin cards' top padding, and capitalise platform names
ci / web (push) Successful in 1m5s
ci / docs-site (push) Successful in 1m5s
apple / swift (push) Successful in 1m24s
decky / build-publish (push) Successful in 19s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 8s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 31s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 9s
docker / build-push (ci, ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 8s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 9s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 10s
ci / bench (push) Successful in 5m34s
apple / screenshots (push) Successful in 6m33s
windows-host / package (push) Successful in 9m46s
deb / build-publish (push) Successful in 9m19s
docker / deploy-docs (push) Successful in 26s
deb / build-publish-host (push) Successful in 9m48s
android / android (push) Successful in 17m6s
arch / build-publish (push) Successful in 18m10s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 15m30s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 21m38s
ci / rust (push) Successful in 27m42s
ci / web (push) Successful in 1m5s
ci / docs-site (push) Successful in 1m5s
apple / swift (push) Successful in 1m24s
decky / build-publish (push) Successful in 19s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 8s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 31s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 9s
docker / build-push (ci, ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 8s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 9s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 10s
ci / bench (push) Successful in 5m34s
apple / screenshots (push) Successful in 6m33s
windows-host / package (push) Successful in 9m46s
deb / build-publish (push) Successful in 9m19s
docker / deploy-docs (push) Successful in 26s
deb / build-publish-host (push) Successful in 9m48s
android / android (push) Successful in 17m6s
arch / build-publish (push) Successful in 18m10s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 15m30s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 21m38s
ci / rust (push) Successful in 27m42s
Two things looked wrong in the store: The cards had no space above the icon. `CardContent` zeroes its own top padding (`pt-0`/`sm:pt-0`) because it normally sits under a `CardHeader` that supplies it — but these cards have no header, so the top padding simply vanished while the other three sides kept `p-card`. `p-card` does not undo it: `card` is a custom `--spacing-*` token, which tailwind-merge does not recognise as a spacing value and so never dedupes against `pt-0`, leaving the longhand to win. Both header-less cards in this section now restore it explicitly, at both breakpoints. Platform chips read "windows" / "linux". Those are the catalog's platform IDENTIFIERS (the index validator pins `linux | windows | macos`), which is right for the wire and wrong on screen; the card now maps them to display names — Linux, Windows, macOS. Proper nouns, so deliberately not routed through i18n. Adds Store/StoreCard stories covering the padding, the platform labels, and the installed / update / incompatible / blocked / external states, so the card can be eyeballed without a host or a catalog.
This commit is contained in:
@@ -127,6 +127,26 @@ export const BrowseTab: FC<{
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The catalog stores platform IDENTIFIERS (`linux | windows | macos` — see the index
|
||||||
|
* validator); these are their display names. Proper nouns, so deliberately not routed
|
||||||
|
* through i18n, and `macos` is spelled the way Apple spells it.
|
||||||
|
*/
|
||||||
|
const PLATFORM_LABELS: Record<string, string> = {
|
||||||
|
linux: "Linux",
|
||||||
|
windows: "Windows",
|
||||||
|
macos: "macOS",
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `CardContent` zeroes its top padding (`pt-0`/`sm:pt-0`) because it normally sits under a
|
||||||
|
* `CardHeader` that already supplies it — these cards have no header, so the top padding
|
||||||
|
* has to come back explicitly, at BOTH breakpoints. `p-card` alone does not do it: `card`
|
||||||
|
* is a custom `--spacing-*` token, which tailwind-merge does not recognise as a spacing
|
||||||
|
* value and therefore never dedupes against `pt-0`, leaving the longhand to win.
|
||||||
|
*/
|
||||||
|
const HEADERLESS_CARD_PADDING = "p-card pt-card sm:pt-card";
|
||||||
|
|
||||||
/** One catalog entry. Blocked entries shout; incompatible ones grey out; neither can be installed. */
|
/** One catalog entry. Blocked entries shout; incompatible ones grey out; neither can be installed. */
|
||||||
export const StoreCard: FC<{ entry: StoreEntry; onInstall: () => void }> = ({
|
export const StoreCard: FC<{ entry: StoreEntry; onInstall: () => void }> = ({
|
||||||
entry,
|
entry,
|
||||||
@@ -145,7 +165,9 @@ export const StoreCard: FC<{ entry: StoreEntry; onInstall: () => void }> = ({
|
|||||||
!entry.compatible && !blocked && "opacity-60",
|
!entry.compatible && !blocked && "opacity-60",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<CardContent className="flex flex-1 flex-col gap-3 p-card">
|
<CardContent
|
||||||
|
className={cn("flex flex-1 flex-col gap-3", HEADERLESS_CARD_PADDING)}
|
||||||
|
>
|
||||||
<div className="flex items-start gap-3">
|
<div className="flex items-start gap-3">
|
||||||
<span className="flex size-10 shrink-0 items-center justify-center rounded-md bg-primary/15">
|
<span className="flex size-10 shrink-0 items-center justify-center rounded-md bg-primary/15">
|
||||||
<Icon className="size-5 text-foreground" />
|
<Icon className="size-5 text-foreground" />
|
||||||
@@ -173,7 +195,7 @@ export const StoreCard: FC<{ entry: StoreEntry; onInstall: () => void }> = ({
|
|||||||
<div className="flex flex-wrap gap-1.5">
|
<div className="flex flex-wrap gap-1.5">
|
||||||
{entry.platforms.map((p) => (
|
{entry.platforms.map((p) => (
|
||||||
<Badge key={p} variant="secondary" className="font-normal">
|
<Badge key={p} variant="secondary" className="font-normal">
|
||||||
{p}
|
{PLATFORM_LABELS[p] ?? p}
|
||||||
</Badge>
|
</Badge>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ export const JobProgressCard: FC<{
|
|||||||
className={failed ? "ring-2 ring-destructive/60" : undefined}
|
className={failed ? "ring-2 ring-destructive/60" : undefined}
|
||||||
aria-live="polite"
|
aria-live="polite"
|
||||||
>
|
>
|
||||||
<CardContent className="space-y-3 p-card">
|
<CardContent className="space-y-3 p-card pt-card sm:pt-card">
|
||||||
<div className="flex items-start gap-3">
|
<div className="flex items-start gap-3">
|
||||||
{running ? (
|
{running ? (
|
||||||
<Spinner className="mt-0.5 size-5 shrink-0" />
|
<Spinner className="mt-0.5 size-5 shrink-0" />
|
||||||
|
|||||||
@@ -0,0 +1,91 @@
|
|||||||
|
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||||
|
import type { StoreEntry } from "@/api/store";
|
||||||
|
import { StoreCard } from "@/sections/Store/Browse";
|
||||||
|
|
||||||
|
// The store catalog card, rendered straight from a fixture entry — it fetches nothing, so
|
||||||
|
// the visuals (padding, platform labels, tier chip, blocked/incompatible states) can be
|
||||||
|
// checked without a host or a catalog.
|
||||||
|
|
||||||
|
const meta = {
|
||||||
|
title: "Store/StoreCard",
|
||||||
|
component: StoreCard,
|
||||||
|
parameters: { layout: "centered" },
|
||||||
|
args: { onInstall: () => {} },
|
||||||
|
} satisfies Meta<typeof StoreCard>;
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
|
const BASE: StoreEntry = {
|
||||||
|
id: "rom-manager",
|
||||||
|
pkg: "@punktfunk/plugin-rom-manager",
|
||||||
|
title: "ROM Manager",
|
||||||
|
description:
|
||||||
|
"Scans your ROM directories, maps them to emulators, fetches box art, and reconciles everything into the host game library as a provider.",
|
||||||
|
icon: "gamepad-2",
|
||||||
|
author: "unom",
|
||||||
|
homepage: "https://git.unom.io/unom/punktfunk-plugin-rom-manager",
|
||||||
|
license: "MIT OR Apache-2.0",
|
||||||
|
version: "0.3.2",
|
||||||
|
source: "unom official",
|
||||||
|
tier: "verified",
|
||||||
|
// The catalog stores lowercase platform IDENTIFIERS; the card renders display names.
|
||||||
|
platforms: ["linux", "windows"],
|
||||||
|
compatible: true,
|
||||||
|
update_available: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Default: Story = { args: { entry: BASE } };
|
||||||
|
|
||||||
|
export const AllPlatforms: Story = {
|
||||||
|
args: { entry: { ...BASE, platforms: ["linux", "windows", "macos"] } },
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SinglePlatform: Story = {
|
||||||
|
args: {
|
||||||
|
entry: {
|
||||||
|
...BASE,
|
||||||
|
id: "playnite",
|
||||||
|
pkg: "@punktfunk/plugin-playnite",
|
||||||
|
title: "Playnite",
|
||||||
|
description:
|
||||||
|
"Syncs your Playnite library into the host game library — every store and emulator Playnite manages, launched back through Playnite.",
|
||||||
|
icon: "library-big",
|
||||||
|
version: "0.2.0",
|
||||||
|
platforms: ["windows"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Installed: Story = {
|
||||||
|
args: { entry: { ...BASE, installed_version: "0.3.2" } },
|
||||||
|
};
|
||||||
|
|
||||||
|
export const UpdateAvailable: Story = {
|
||||||
|
args: {
|
||||||
|
entry: { ...BASE, installed_version: "0.3.1", update_available: true },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Incompatible: Story = {
|
||||||
|
args: {
|
||||||
|
entry: {
|
||||||
|
...BASE,
|
||||||
|
compatible: false,
|
||||||
|
incompatible_reason:
|
||||||
|
"needs punktfunk host ≥ 0.16.0 (this host is 0.15.0)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Blocked: Story = {
|
||||||
|
args: {
|
||||||
|
entry: { ...BASE, blocked: "withdrawn by the author (CVE-2026-1234)" },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const External: Story = {
|
||||||
|
args: {
|
||||||
|
entry: { ...BASE, tier: "external", source: "community catalog" },
|
||||||
|
},
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user