mirror of
https://github.com/github/awesome-copilot.git
synced 2026-06-25 17:00:20 +00:00
Consolidate listing pages with unified grid cards and modal system (#2101)
* Prototype extension details modal - Add detail popup modal for extension cards with full metadata and gallery - Implement image gallery with thumbnail strip and main image selection - Add modal styling and positioning in global.css - Connect card click handlers to open modal with extension data Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix accessibility issues with modal focus restoration - Add missing listing-cards-page class to agents.astro page root - Pass focusable button element to openCardDetailsModal instead of article - Fixes focus restoration for keyboard users when closing modal - Applied fix across all listing pages (agents, instructions, hooks, plugins, skills, workflows) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address remaining PR review feedback - Fix extension modal ARIA state by setting aria-current to "true" and removing it when inactive - Use focusable .resource-preview as modal trigger for extension thumbnail/click/keyboard paths - Extract shared multi-select helpers into pages/select-utils.ts and reuse across instructions/hooks/plugins/workflows - Remove unused card-model.ts to avoid dead/overlapping type definitions Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { escapeHtml, getGitHubUrl, getLastUpdatedHtml } from "../utils";
|
||||
import { renderEmptyStateHtml, renderSharedCardHtml } from "./card-render";
|
||||
|
||||
export interface RenderableExtension {
|
||||
id: string;
|
||||
@@ -17,10 +18,16 @@ export interface RenderableExtension {
|
||||
path?: string | null;
|
||||
type?: string | null;
|
||||
} | null;
|
||||
gallery?: {
|
||||
path?: string | null;
|
||||
type?: string | null;
|
||||
} | null;
|
||||
gallery?:
|
||||
| {
|
||||
path?: string | null;
|
||||
type?: string | null;
|
||||
}
|
||||
| Array<{
|
||||
path?: string | null;
|
||||
type?: string | null;
|
||||
}>
|
||||
| null;
|
||||
} | null;
|
||||
imageUrl?: string | null;
|
||||
assetPath?: string | null;
|
||||
@@ -48,12 +55,10 @@ export function sortExtensions<T extends RenderableExtension>(
|
||||
|
||||
export function renderExtensionsHtml(items: RenderableExtension[]): string {
|
||||
if (items.length === 0) {
|
||||
return `
|
||||
<div class="empty-state">
|
||||
<h3>No extensions found</h3>
|
||||
<p>No canvas extensions are available right now.</p>
|
||||
</div>
|
||||
`;
|
||||
return renderEmptyStateHtml(
|
||||
"No extensions found",
|
||||
"No canvas extensions are available right now."
|
||||
);
|
||||
}
|
||||
|
||||
return items
|
||||
@@ -69,62 +74,60 @@ export function renderExtensionsHtml(items: RenderableExtension[]): string {
|
||||
const sourceUrl =
|
||||
item.sourceUrl || (item.path ? getGitHubUrl(item.path) : "");
|
||||
|
||||
return `
|
||||
<article id="${escapeHtml(item.id)}" class="resource-item" role="listitem">
|
||||
<div class="resource-preview">
|
||||
${
|
||||
item.imageUrl
|
||||
? `<button type="button" class="resource-thumbnail-btn" data-preview-url="${escapeHtml(item.imageUrl)}" data-preview-alt="${escapeHtml(item.name)} preview" aria-label="Open ${escapeHtml(item.name)} preview">
|
||||
<img class="resource-thumbnail" src="${escapeHtml(item.imageUrl)}" alt="${escapeHtml(item.name)} preview" loading="lazy" />
|
||||
</button>`
|
||||
: `<div class="resource-thumbnail resource-thumbnail-placeholder" aria-hidden="true">Canvas</div>`
|
||||
}
|
||||
<div class="resource-info">
|
||||
<div class="resource-title">${escapeHtml(item.name)}</div>
|
||||
<div class="resource-description">${escapeHtml(
|
||||
item.description || "Canvas extension"
|
||||
)}</div>
|
||||
<div class="resource-keywords">
|
||||
${
|
||||
item.keywords && item.keywords.length > 0
|
||||
? item.keywords
|
||||
.map(
|
||||
(kw) =>
|
||||
`<span class="keyword-tag">${escapeHtml(kw)}</span>`
|
||||
)
|
||||
.join("")
|
||||
: ""
|
||||
}
|
||||
</div>
|
||||
<div class="resource-meta">
|
||||
${
|
||||
item.external
|
||||
? '<span class="resource-tag">External</span>'
|
||||
: ""
|
||||
}
|
||||
${getLastUpdatedHtml(item.lastUpdated)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="resource-actions">
|
||||
<button
|
||||
class="btn btn-primary btn-small copy-install-url-btn"
|
||||
data-install-url="${escapeHtml(installUrl)}"
|
||||
title="Copy install URL"
|
||||
${installUrl ? "" : "disabled"}
|
||||
>
|
||||
Install
|
||||
</button>
|
||||
${
|
||||
sourceUrl
|
||||
? `<a href="${escapeHtml(
|
||||
sourceUrl
|
||||
)}" class="btn btn-secondary btn-small" target="_blank" rel="noopener noreferrer" title="View source">Source</a>`
|
||||
: ""
|
||||
}
|
||||
</div>
|
||||
</article>
|
||||
const previewMediaHtml = item.imageUrl
|
||||
? `<div class="resource-thumbnail-btn" data-extension-id="${escapeHtml(item.id)}" aria-hidden="true">
|
||||
<img class="resource-thumbnail" src="${escapeHtml(item.imageUrl)}" alt="${escapeHtml(item.name)} preview" loading="lazy" />
|
||||
</div>`
|
||||
: `<div class="resource-thumbnail resource-thumbnail-placeholder" aria-hidden="true">Canvas</div>`;
|
||||
|
||||
const infoExtraHtml = `
|
||||
<div class="resource-keywords">
|
||||
${
|
||||
item.keywords && item.keywords.length > 0
|
||||
? item.keywords
|
||||
.map((kw) => `<span class="keyword-tag">${escapeHtml(kw)}</span>`)
|
||||
.join("")
|
||||
: ""
|
||||
}
|
||||
</div>
|
||||
`;
|
||||
|
||||
const metaHtml = `
|
||||
${item.external ? '<span class="resource-tag">External</span>' : ""}
|
||||
${getLastUpdatedHtml(item.lastUpdated)}
|
||||
`;
|
||||
|
||||
const actionsHtml = `
|
||||
<button
|
||||
class="btn btn-primary btn-small copy-install-url-btn"
|
||||
data-install-url="${escapeHtml(installUrl)}"
|
||||
title="Copy install URL"
|
||||
${installUrl ? "" : "disabled"}
|
||||
>
|
||||
Install
|
||||
</button>
|
||||
${
|
||||
sourceUrl
|
||||
? `<a href="${escapeHtml(
|
||||
sourceUrl
|
||||
)}" class="btn btn-secondary btn-small" target="_blank" rel="noopener noreferrer" title="View source">Source</a>`
|
||||
: ""
|
||||
}
|
||||
`;
|
||||
|
||||
return renderSharedCardHtml({
|
||||
title: item.name,
|
||||
description: item.description || "Canvas extension",
|
||||
previewMediaHtml,
|
||||
infoExtraHtml,
|
||||
metaHtml,
|
||||
actionsHtml,
|
||||
tabIndex: 0,
|
||||
articleAttributes: {
|
||||
id: item.id,
|
||||
"data-extension-id": item.id,
|
||||
},
|
||||
});
|
||||
})
|
||||
.join("");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user