mirror of
https://github.com/github/awesome-copilot.git
synced 2026-02-20 02:15:12 +00:00
feat(website): add comprehensive accessibility improvements
Phase 1 - Screen Reader Critical: - Add aria-label to main navigation - Add accessible names to icon-only buttons (GitHub, theme toggle, close) - Add aria-hidden to decorative SVGs and emoji icons - Add role=dialog, aria-modal, aria-labelledby to modal - Add skip link with visible focus state Phase 2 - Keyboard Navigation: - Implement focus trap in modal (Tab/Shift+Tab cycles) - Return focus to trigger element on modal close - Replace outline:none with visible focus rings - Add keyboard navigation to install dropdown (arrows, escape) - Add aria-expanded to dropdown toggles Phase 3 - Dynamic Content: - Add aria-live=polite to results counts and loading states - Add role=listbox to search results - Add role=list to resource lists - Add role=menu/menuitem to dropdown menus Phase 4 - Forms & Labels: - Add .sr-only utility class for screen reader text - Add visually hidden labels to all search inputs - Add aria-label to filter dropdowns Files modified: - BaseLayout.astro, Modal.astro (ARIA attributes) - modal.ts (focus trap, keyboard navigation) - global.css (sr-only, skip-link, focus styles) - All page files (labels, live regions, roles)
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"generated": "2026-02-01T23:32:16.397Z",
|
||||
"generated": "2026-02-01T23:35:23.497Z",
|
||||
"counts": {
|
||||
"agents": 140,
|
||||
"prompts": 134,
|
||||
|
||||
@@ -110,6 +110,39 @@ body {
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* Accessibility Utilities */
|
||||
.sr-only {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.skip-link {
|
||||
position: absolute;
|
||||
top: -100%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background: var(--color-accent);
|
||||
color: var(--color-text-emphasis);
|
||||
padding: 12px 24px;
|
||||
border-radius: var(--border-radius);
|
||||
font-weight: 600;
|
||||
z-index: 10000;
|
||||
transition: top var(--transition);
|
||||
}
|
||||
|
||||
.skip-link:focus {
|
||||
top: 12px;
|
||||
outline: 2px solid var(--color-text-emphasis);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: var(--container-width);
|
||||
margin: 0 auto;
|
||||
@@ -348,7 +381,8 @@ a:hover {
|
||||
}
|
||||
|
||||
.hero-search input:focus {
|
||||
outline: none;
|
||||
outline: 2px solid var(--color-accent);
|
||||
outline-offset: 2px;
|
||||
border-color: var(--color-accent);
|
||||
box-shadow: var(--shadow-glow);
|
||||
}
|
||||
@@ -651,6 +685,20 @@ a:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.btn:focus {
|
||||
outline: 2px solid var(--color-accent);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.btn:focus:not(:focus-visible) {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.btn:focus-visible {
|
||||
outline: 2px solid var(--color-accent);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: var(--gradient-primary);
|
||||
color: white;
|
||||
@@ -1024,7 +1072,8 @@ a:hover {
|
||||
}
|
||||
|
||||
.search-bar input:focus {
|
||||
outline: none;
|
||||
outline: 2px solid var(--color-accent);
|
||||
outline-offset: 2px;
|
||||
border-color: var(--color-accent);
|
||||
box-shadow: var(--shadow-glow);
|
||||
}
|
||||
@@ -1068,7 +1117,8 @@ a:hover {
|
||||
}
|
||||
|
||||
.filter-group select:focus {
|
||||
outline: none;
|
||||
outline: 2px solid var(--color-accent);
|
||||
outline-offset: 2px;
|
||||
border-color: var(--color-accent);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,50 +2,50 @@
|
||||
// Modal component for viewing file contents
|
||||
---
|
||||
|
||||
<div id="file-modal" class="modal hidden">
|
||||
<div id="file-modal" class="modal hidden" role="dialog" aria-modal="true" aria-labelledby="modal-title">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h3 id="modal-title">File</h3>
|
||||
<div class="modal-actions">
|
||||
<button id="copy-btn" class="btn btn-secondary" title="Copy to clipboard">
|
||||
<svg viewBox="0 0 16 16" width="16" height="16" fill="currentColor">
|
||||
<button id="copy-btn" class="btn btn-secondary" aria-label="Copy to clipboard">
|
||||
<svg viewBox="0 0 16 16" width="16" height="16" fill="currentColor" aria-hidden="true">
|
||||
<path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z"/>
|
||||
<path d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z"/>
|
||||
</svg>
|
||||
Copy
|
||||
<span aria-hidden="true">Copy</span>
|
||||
</button>
|
||||
<button id="download-btn" class="btn btn-secondary" title="Download file">
|
||||
<svg viewBox="0 0 16 16" width="16" height="16" fill="currentColor">
|
||||
<button id="download-btn" class="btn btn-secondary" aria-label="Download file">
|
||||
<svg viewBox="0 0 16 16" width="16" height="16" fill="currentColor" aria-hidden="true">
|
||||
<path d="M7.47 10.78a.75.75 0 0 0 1.06 0l3.75-3.75a.75.75 0 0 0-1.06-1.06L8.75 8.44V1.75a.75.75 0 0 0-1.5 0v6.69L4.78 5.97a.75.75 0 0 0-1.06 1.06l3.75 3.75ZM3.75 13a.75.75 0 0 0 0 1.5h8.5a.75.75 0 0 0 0-1.5h-8.5Z"/>
|
||||
</svg>
|
||||
Download
|
||||
<span aria-hidden="true">Download</span>
|
||||
</button>
|
||||
<button id="share-btn" class="btn btn-secondary" title="Copy link to clipboard">
|
||||
<svg viewBox="0 0 16 16" width="16" height="16" fill="currentColor">
|
||||
<button id="share-btn" class="btn btn-secondary" aria-label="Copy link to clipboard">
|
||||
<svg viewBox="0 0 16 16" width="16" height="16" fill="currentColor" aria-hidden="true">
|
||||
<path d="M7.775 3.275a.75.75 0 0 0 1.06 1.06l1.25-1.25a2 2 0 1 1 2.83 2.83l-2.5 2.5a2 2 0 0 1-2.83 0 .75.75 0 0 0-1.06 1.06 3.5 3.5 0 0 0 4.95 0l2.5-2.5a3.5 3.5 0 0 0-4.95-4.95l-1.25 1.25zm-.025 5.45a.75.75 0 0 0-1.06-1.06l-1.25 1.25a2 2 0 1 1-2.83-2.83l2.5-2.5a2 2 0 0 1 2.83 0 .75.75 0 0 0 1.06-1.06 3.5 3.5 0 0 0-4.95 0l-2.5 2.5a3.5 3.5 0 0 0 4.95 4.95l1.25-1.25z"/>
|
||||
</svg>
|
||||
Share
|
||||
<span aria-hidden="true">Share</span>
|
||||
</button>
|
||||
<div id="install-dropdown" class="install-dropdown" style="display: none;">
|
||||
<a id="install-btn-main" class="btn btn-primary install-btn-main" target="_blank" rel="noopener">
|
||||
Install
|
||||
</a>
|
||||
<button type="button" class="btn btn-primary install-btn-toggle" aria-label="Install options">
|
||||
<svg viewBox="0 0 16 16" width="12" height="12" fill="currentColor">
|
||||
<button type="button" class="btn btn-primary install-btn-toggle" aria-label="Install options" aria-expanded="false" aria-haspopup="true">
|
||||
<svg viewBox="0 0 16 16" width="12" height="12" fill="currentColor" aria-hidden="true">
|
||||
<path d="M4.427 7.427l3.396 3.396a.25.25 0 00.354 0l3.396-3.396A.25.25 0 0011.396 7H4.604a.25.25 0 00-.177.427z"/>
|
||||
</svg>
|
||||
</button>
|
||||
<div class="install-dropdown-menu">
|
||||
<a id="install-vscode" target="_blank" rel="noopener">
|
||||
<div class="install-dropdown-menu" role="menu">
|
||||
<a id="install-vscode" target="_blank" rel="noopener" role="menuitem">
|
||||
VS Code
|
||||
</a>
|
||||
<a id="install-insiders" target="_blank" rel="noopener">
|
||||
<a id="install-insiders" target="_blank" rel="noopener" role="menuitem">
|
||||
VS Code Insiders
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<button id="close-modal" class="btn btn-icon" title="Close">
|
||||
<svg viewBox="0 0 16 16" width="16" height="16" fill="currentColor">
|
||||
<button id="close-modal" class="btn btn-icon" aria-label="Close dialog">
|
||||
<svg viewBox="0 0 16 16" width="16" height="16" fill="currentColor" aria-hidden="true">
|
||||
<path d="M3.72 3.72a.75.75 0 011.06 0L8 6.94l3.22-3.22a.75.75 0 111.06 1.06L9.06 8l3.22 3.22a.75.75 0 11-1.06 1.06L8 9.06l-3.22 3.22a.75.75 0 01-1.06-1.06L6.94 8 3.72 4.78a.75.75 0 010-1.06z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
@@ -28,15 +28,16 @@ const base = import.meta.env.BASE_URL;
|
||||
</script>
|
||||
</head>
|
||||
<body data-base-path={base}>
|
||||
<a href="#main-content" class="skip-link">Skip to main content</a>
|
||||
<header class="site-header">
|
||||
<div class="container">
|
||||
<div class="header-content">
|
||||
<a href={base} class="logo">
|
||||
<img src={`${base}images/Copilot_Icon_White.svg`} alt="GitHub Copilot" class="logo-icon logo-icon-dark" width="32" height="32" />
|
||||
<img src={`${base}images/Copilot_Icon_Black.svg`} alt="GitHub Copilot" class="logo-icon logo-icon-light" width="32" height="32" />
|
||||
<img src={`${base}images/Copilot_Icon_White.svg`} alt="" class="logo-icon logo-icon-dark" width="32" height="32" aria-hidden="true" />
|
||||
<img src={`${base}images/Copilot_Icon_Black.svg`} alt="" class="logo-icon logo-icon-light" width="32" height="32" aria-hidden="true" />
|
||||
<span class="logo-text">Awesome Copilot</span>
|
||||
</a>
|
||||
<nav class="main-nav">
|
||||
<nav class="main-nav" aria-label="Main navigation">
|
||||
<a href={`${base}agents/`} class:list={[{ active: activeNav === 'agents' }]}>Agents</a>
|
||||
<a href={`${base}prompts/`} class:list={[{ active: activeNav === 'prompts' }]}>Prompts</a>
|
||||
<a href={`${base}instructions/`} class:list={[{ active: activeNav === 'instructions' }]}>Instructions</a>
|
||||
@@ -46,16 +47,16 @@ const base = import.meta.env.BASE_URL;
|
||||
<a href={`${base}samples/`} class:list={[{ active: activeNav === 'samples' }]}>Samples</a>
|
||||
</nav>
|
||||
<div class="header-actions">
|
||||
<button id="theme-toggle" class="theme-toggle" title="Toggle theme">
|
||||
<svg class="icon-sun" viewBox="0 0 16 16" fill="currentColor">
|
||||
<button id="theme-toggle" class="theme-toggle" aria-label="Toggle theme">
|
||||
<svg class="icon-sun" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true">
|
||||
<path d="M8 12a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM8 0a.75.75 0 0 1 .75.75v1.5a.75.75 0 0 1-1.5 0V.75A.75.75 0 0 1 8 0zm0 13a.75.75 0 0 1 .75.75v1.5a.75.75 0 0 1-1.5 0v-1.5A.75.75 0 0 1 8 13zM2.343 2.343a.75.75 0 0 1 1.061 0l1.06 1.061a.75.75 0 0 1-1.06 1.06l-1.06-1.06a.75.75 0 0 1 0-1.06zm9.193 9.193a.75.75 0 0 1 1.06 0l1.061 1.06a.75.75 0 0 1-1.06 1.061l-1.061-1.06a.75.75 0 0 1 0-1.061zM0 8a.75.75 0 0 1 .75-.75h1.5a.75.75 0 0 1 0 1.5H.75A.75.75 0 0 1 0 8zm13 0a.75.75 0 0 1 .75-.75h1.5a.75.75 0 0 1 0 1.5h-1.5A.75.75 0 0 1 13 8zM2.343 13.657a.75.75 0 0 1 0-1.061l1.06-1.06a.75.75 0 0 1 1.061 1.06l-1.06 1.06a.75.75 0 0 1-1.061 0zm9.193-9.193a.75.75 0 0 1 0-1.06l1.061-1.061a.75.75 0 0 1 1.06 1.06l-1.06 1.061a.75.75 0 0 1-1.061 0z"/>
|
||||
</svg>
|
||||
<svg class="icon-moon" viewBox="0 0 16 16" fill="currentColor">
|
||||
<svg class="icon-moon" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true">
|
||||
<path d="M9.598 1.591a.75.75 0 0 1 .785-.175 7 7 0 1 1-8.967 8.967.75.75 0 0 1 .961-.96 5.5 5.5 0 0 0 7.046-7.046.75.75 0 0 1 .175-.786zm1.616 1.945a7 7 0 0 1-7.678 7.678 5.5 5.5 0 1 0 7.678-7.678z"/>
|
||||
</svg>
|
||||
</button>
|
||||
<a href="https://github.com/github/awesome-copilot" class="github-link" target="_blank" rel="noopener">
|
||||
<svg viewBox="0 0 16 16" width="24" height="24" fill="currentColor">
|
||||
<a href="https://github.com/github/awesome-copilot" class="github-link" target="_blank" rel="noopener" aria-label="View on GitHub">
|
||||
<svg viewBox="0 0 16 16" width="24" height="24" fill="currentColor" aria-hidden="true">
|
||||
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"></path>
|
||||
</svg>
|
||||
</a>
|
||||
|
||||
@@ -4,7 +4,7 @@ import Modal from '../components/Modal.astro';
|
||||
---
|
||||
|
||||
<BaseLayout title="Agents" description="Specialized agents that enhance GitHub Copilot for specific technologies, workflows, and domains" activeNav="agents">
|
||||
<main>
|
||||
<main id="main-content">
|
||||
<div class="page-header">
|
||||
<div class="container">
|
||||
<h1>🤖 Custom Agents</h1>
|
||||
@@ -15,6 +15,7 @@ import Modal from '../components/Modal.astro';
|
||||
<div class="page-content">
|
||||
<div class="container">
|
||||
<div class="search-bar">
|
||||
<label for="search-input" class="sr-only">Search agents</label>
|
||||
<input type="text" id="search-input" placeholder="Search agents..." autocomplete="off">
|
||||
</div>
|
||||
|
||||
@@ -22,11 +23,11 @@ import Modal from '../components/Modal.astro';
|
||||
<div class="filters-bar" id="filters-bar">
|
||||
<div class="filter-group">
|
||||
<label for="filter-model">Model:</label>
|
||||
<select id="filter-model" multiple></select>
|
||||
<select id="filter-model" multiple aria-label="Filter by model"></select>
|
||||
</div>
|
||||
<div class="filter-group">
|
||||
<label for="filter-tool">Tool:</label>
|
||||
<select id="filter-tool" multiple></select>
|
||||
<select id="filter-tool" multiple aria-label="Filter by tool"></select>
|
||||
</div>
|
||||
<div class="filter-group">
|
||||
<label class="checkbox-label">
|
||||
@@ -37,9 +38,9 @@ import Modal from '../components/Modal.astro';
|
||||
<button id="clear-filters" class="btn btn-secondary btn-small">Clear Filters</button>
|
||||
</div>
|
||||
|
||||
<div class="results-count" id="results-count"></div>
|
||||
<div class="resource-list" id="resource-list">
|
||||
<div class="loading">Loading agents...</div>
|
||||
<div class="results-count" id="results-count" aria-live="polite"></div>
|
||||
<div class="resource-list" id="resource-list" role="list">
|
||||
<div class="loading" aria-live="polite">Loading agents...</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -4,7 +4,7 @@ import Modal from '../components/Modal.astro';
|
||||
---
|
||||
|
||||
<BaseLayout title="Collections" description="Curated collections of prompts, instructions, and agents for specific workflows" activeNav="collections">
|
||||
<main>
|
||||
<main id="main-content">
|
||||
<div class="page-header">
|
||||
<div class="container">
|
||||
<h1>📦 Collections</h1>
|
||||
@@ -15,13 +15,14 @@ import Modal from '../components/Modal.astro';
|
||||
<div class="page-content">
|
||||
<div class="container">
|
||||
<div class="search-bar">
|
||||
<label for="search-input" class="sr-only">Search collections</label>
|
||||
<input type="text" id="search-input" placeholder="Search collections..." autocomplete="off">
|
||||
</div>
|
||||
|
||||
<div class="filters-bar" id="filters-bar">
|
||||
<div class="filter-group">
|
||||
<label for="filter-tag">Tag:</label>
|
||||
<select id="filter-tag" multiple></select>
|
||||
<select id="filter-tag" multiple aria-label="Filter by tag"></select>
|
||||
</div>
|
||||
<div class="filter-group">
|
||||
<label class="checkbox-label">
|
||||
@@ -32,9 +33,9 @@ import Modal from '../components/Modal.astro';
|
||||
<button id="clear-filters" class="btn btn-secondary btn-small">Clear Filters</button>
|
||||
</div>
|
||||
|
||||
<div class="results-count" id="results-count"></div>
|
||||
<div class="resource-list" id="resource-list">
|
||||
<div class="loading">Loading collections...</div>
|
||||
<div class="results-count" id="results-count" aria-live="polite"></div>
|
||||
<div class="resource-list" id="resource-list" role="list">
|
||||
<div class="loading" aria-live="polite">Loading collections...</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -6,102 +6,104 @@ const base = import.meta.env.BASE_URL;
|
||||
---
|
||||
|
||||
<BaseLayout title="Home" activeNav="">
|
||||
<main>
|
||||
<main id="main-content">
|
||||
<!-- Hero Section -->
|
||||
<section class="hero">
|
||||
<section class="hero" aria-labelledby="hero-heading">
|
||||
<div class="container">
|
||||
<h1>Awesome GitHub Copilot</h1>
|
||||
<h1 id="hero-heading">Awesome GitHub Copilot</h1>
|
||||
<p class="hero-subtitle">Community-contributed instructions, prompts, agents, and skills to enhance your GitHub Copilot experience</p>
|
||||
<div class="hero-search">
|
||||
<input type="text" id="global-search" placeholder="Search all resources..." autocomplete="off">
|
||||
<div id="search-results" class="search-results hidden"></div>
|
||||
<label for="global-search" class="sr-only">Search all resources</label>
|
||||
<input type="text" id="global-search" placeholder="Search all resources..." autocomplete="off" role="combobox" aria-autocomplete="list" aria-expanded="false" aria-controls="search-results">
|
||||
<div id="search-results" class="search-results hidden" role="listbox" aria-label="Search results"></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Quick Links -->
|
||||
<section class="quick-links">
|
||||
<section class="quick-links" aria-labelledby="quick-links-heading">
|
||||
<h2 id="quick-links-heading" class="sr-only">Browse Resources</h2>
|
||||
<div class="container">
|
||||
<div class="cards-grid">
|
||||
<a href={`${base}agents/`} class="card card-with-count" id="card-agents">
|
||||
<div class="card-icon">🤖</div>
|
||||
<div class="card-icon" aria-hidden="true">🤖</div>
|
||||
<div class="card-content">
|
||||
<h3>Agents</h3>
|
||||
<p>Custom agents for specialized Copilot experiences</p>
|
||||
</div>
|
||||
<div class="card-count" data-count="agents">-</div>
|
||||
<div class="card-count" data-count="agents" aria-label="Agent count">-</div>
|
||||
</a>
|
||||
<a href={`${base}prompts/`} class="card card-with-count" id="card-prompts">
|
||||
<div class="card-icon">🎯</div>
|
||||
<div class="card-icon" aria-hidden="true">🎯</div>
|
||||
<div class="card-content">
|
||||
<h3>Prompts</h3>
|
||||
<p>Ready-to-use prompt templates for development tasks</p>
|
||||
</div>
|
||||
<div class="card-count" data-count="prompts">-</div>
|
||||
<div class="card-count" data-count="prompts" aria-label="Prompt count">-</div>
|
||||
</a>
|
||||
<a href={`${base}instructions/`} class="card card-with-count" id="card-instructions">
|
||||
<div class="card-icon">📋</div>
|
||||
<div class="card-icon" aria-hidden="true">📋</div>
|
||||
<div class="card-content">
|
||||
<h3>Instructions</h3>
|
||||
<p>Coding standards and best practices for Copilot</p>
|
||||
</div>
|
||||
<div class="card-count" data-count="instructions">-</div>
|
||||
<div class="card-count" data-count="instructions" aria-label="Instruction count">-</div>
|
||||
</a>
|
||||
<a href={`${base}skills/`} class="card card-with-count" id="card-skills">
|
||||
<div class="card-icon">⚡</div>
|
||||
<div class="card-icon" aria-hidden="true">⚡</div>
|
||||
<div class="card-content">
|
||||
<h3>Skills</h3>
|
||||
<p>Self-contained folders with instructions and resources</p>
|
||||
</div>
|
||||
<div class="card-count" data-count="skills">-</div>
|
||||
<div class="card-count" data-count="skills" aria-label="Skill count">-</div>
|
||||
</a>
|
||||
<a href={`${base}collections/`} class="card card-with-count" id="card-collections">
|
||||
<div class="card-icon">📦</div>
|
||||
<div class="card-icon" aria-hidden="true">📦</div>
|
||||
<div class="card-content">
|
||||
<h3>Collections</h3>
|
||||
<p>Curated collections organized by themes</p>
|
||||
</div>
|
||||
<div class="card-count" data-count="collections">-</div>
|
||||
<div class="card-count" data-count="collections" aria-label="Collection count">-</div>
|
||||
</a>
|
||||
<a href={`${base}tools/`} class="card card-with-count" id="card-tools">
|
||||
<div class="card-icon">🔧</div>
|
||||
<div class="card-icon" aria-hidden="true">🔧</div>
|
||||
<div class="card-content">
|
||||
<h3>Tools</h3>
|
||||
<p>MCP servers and developer tools</p>
|
||||
</div>
|
||||
<div class="card-count" data-count="tools">-</div>
|
||||
<div class="card-count" data-count="tools" aria-label="Tool count">-</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Featured Collections -->
|
||||
<section class="featured">
|
||||
<section class="featured" aria-labelledby="featured-heading">
|
||||
<div class="container">
|
||||
<h2>Featured Collections</h2>
|
||||
<div id="featured-collections" class="cards-grid">
|
||||
<h2 id="featured-heading">Featured Collections</h2>
|
||||
<div id="featured-collections" class="cards-grid" aria-live="polite">
|
||||
<!-- Populated by JS -->
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Getting Started -->
|
||||
<section class="getting-started">
|
||||
<section class="getting-started" aria-labelledby="getting-started-heading">
|
||||
<div class="container">
|
||||
<h2>Getting Started</h2>
|
||||
<h2 id="getting-started-heading">Getting Started</h2>
|
||||
<div class="steps">
|
||||
<div class="step">
|
||||
<div class="step-number">1</div>
|
||||
<div class="step-number" aria-hidden="true">1</div>
|
||||
<h3>Browse</h3>
|
||||
<p>Explore agents, prompts, instructions, and skills</p>
|
||||
</div>
|
||||
<div class="step">
|
||||
<div class="step-number">2</div>
|
||||
<div class="step-number" aria-hidden="true">2</div>
|
||||
<h3>Preview</h3>
|
||||
<p>Click any item to view its full content</p>
|
||||
</div>
|
||||
<div class="step">
|
||||
<div class="step-number">3</div>
|
||||
<div class="step-number" aria-hidden="true">3</div>
|
||||
<h3>Install</h3>
|
||||
<p>One-click install to VS Code or copy to clipboard</p>
|
||||
</div>
|
||||
|
||||
@@ -4,7 +4,7 @@ import Modal from '../components/Modal.astro';
|
||||
---
|
||||
|
||||
<BaseLayout title="Instructions" description="Coding standards and best practices for GitHub Copilot" activeNav="instructions">
|
||||
<main>
|
||||
<main id="main-content">
|
||||
<div class="page-header">
|
||||
<div class="container">
|
||||
<h1>📋 Instructions</h1>
|
||||
@@ -15,20 +15,21 @@ import Modal from '../components/Modal.astro';
|
||||
<div class="page-content">
|
||||
<div class="container">
|
||||
<div class="search-bar">
|
||||
<label for="search-input" class="sr-only">Search instructions</label>
|
||||
<input type="text" id="search-input" placeholder="Search instructions..." autocomplete="off">
|
||||
</div>
|
||||
|
||||
<div class="filters-bar" id="filters-bar">
|
||||
<div class="filter-group">
|
||||
<label for="filter-extension">File Extension:</label>
|
||||
<select id="filter-extension" multiple></select>
|
||||
<select id="filter-extension" multiple aria-label="Filter by file extension"></select>
|
||||
</div>
|
||||
<button id="clear-filters" class="btn btn-secondary btn-small">Clear Filters</button>
|
||||
</div>
|
||||
|
||||
<div class="results-count" id="results-count"></div>
|
||||
<div class="resource-list" id="resource-list">
|
||||
<div class="loading">Loading instructions...</div>
|
||||
<div class="results-count" id="results-count" aria-live="polite"></div>
|
||||
<div class="resource-list" id="resource-list" role="list">
|
||||
<div class="loading" aria-live="polite">Loading instructions...</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -4,7 +4,7 @@ import Modal from '../components/Modal.astro';
|
||||
---
|
||||
|
||||
<BaseLayout title="Prompts" description="Ready-to-use prompt templates for development tasks with GitHub Copilot" activeNav="prompts">
|
||||
<main>
|
||||
<main id="main-content">
|
||||
<div class="page-header">
|
||||
<div class="container">
|
||||
<h1>🎯 Prompts</h1>
|
||||
@@ -15,20 +15,21 @@ import Modal from '../components/Modal.astro';
|
||||
<div class="page-content">
|
||||
<div class="container">
|
||||
<div class="search-bar">
|
||||
<label for="search-input" class="sr-only">Search prompts</label>
|
||||
<input type="text" id="search-input" placeholder="Search prompts..." autocomplete="off">
|
||||
</div>
|
||||
|
||||
<div class="filters-bar" id="filters-bar">
|
||||
<div class="filter-group">
|
||||
<label for="filter-tool">Tool:</label>
|
||||
<select id="filter-tool" multiple></select>
|
||||
<select id="filter-tool" multiple aria-label="Filter by tool"></select>
|
||||
</div>
|
||||
<button id="clear-filters" class="btn btn-secondary btn-small">Clear Filters</button>
|
||||
</div>
|
||||
|
||||
<div class="results-count" id="results-count"></div>
|
||||
<div class="resource-list" id="resource-list">
|
||||
<div class="loading">Loading prompts...</div>
|
||||
<div class="results-count" id="results-count" aria-live="polite"></div>
|
||||
<div class="resource-list" id="resource-list" role="list">
|
||||
<div class="loading" aria-live="polite">Loading prompts...</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,7 @@ const base = import.meta.env.BASE_URL;
|
||||
---
|
||||
|
||||
<BaseLayout title="Samples" description="Code samples and examples for building with GitHub Copilot" activeNav="samples">
|
||||
<main>
|
||||
<main id="main-content">
|
||||
<div class="page-header">
|
||||
<div class="container">
|
||||
<h1>📚 Samples</h1>
|
||||
@@ -16,7 +16,7 @@ const base = import.meta.env.BASE_URL;
|
||||
<div class="page-content">
|
||||
<div class="container">
|
||||
<div class="coming-soon">
|
||||
<div class="coming-soon-icon">🚧</div>
|
||||
<div class="coming-soon-icon" aria-hidden="true">🚧</div>
|
||||
<h2>Coming Soon</h2>
|
||||
<p>We're migrating code samples from the <a href="https://github.com/github/copilot-sdk/tree/main/cookbook" target="_blank" rel="noopener">Copilot SDK Cookbook</a> to this repository.</p>
|
||||
<p>Check back soon for examples including:</p>
|
||||
|
||||
@@ -4,7 +4,7 @@ import Modal from '../components/Modal.astro';
|
||||
---
|
||||
|
||||
<BaseLayout title="Skills" description="Self-contained agent skills with instructions and bundled resources" activeNav="skills">
|
||||
<main>
|
||||
<main id="main-content">
|
||||
<div class="page-header">
|
||||
<div class="container">
|
||||
<h1>⚡ Skills</h1>
|
||||
@@ -15,13 +15,14 @@ import Modal from '../components/Modal.astro';
|
||||
<div class="page-content">
|
||||
<div class="container">
|
||||
<div class="search-bar">
|
||||
<label for="search-input" class="sr-only">Search skills</label>
|
||||
<input type="text" id="search-input" placeholder="Search skills..." autocomplete="off">
|
||||
</div>
|
||||
|
||||
<div class="filters-bar" id="filters-bar">
|
||||
<div class="filter-group">
|
||||
<label for="filter-category">Category:</label>
|
||||
<select id="filter-category" multiple></select>
|
||||
<select id="filter-category" multiple aria-label="Filter by category"></select>
|
||||
</div>
|
||||
<div class="filter-group">
|
||||
<label class="checkbox-label">
|
||||
@@ -32,9 +33,9 @@ import Modal from '../components/Modal.astro';
|
||||
<button id="clear-filters" class="btn btn-secondary btn-small">Clear Filters</button>
|
||||
</div>
|
||||
|
||||
<div class="results-count" id="results-count"></div>
|
||||
<div class="resource-list" id="resource-list">
|
||||
<div class="loading">Loading skills...</div>
|
||||
<div class="results-count" id="results-count" aria-live="polite"></div>
|
||||
<div class="resource-list" id="resource-list" role="list">
|
||||
<div class="loading" aria-live="polite">Loading skills...</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -7,7 +7,7 @@ import BaseLayout from "../layouts/BaseLayout.astro";
|
||||
description="MCP servers and developer tools for GitHub Copilot"
|
||||
activeNav="tools"
|
||||
>
|
||||
<main>
|
||||
<main id="main-content">
|
||||
<div class="page-header">
|
||||
<div class="container">
|
||||
<h1>🔧 Tools</h1>
|
||||
@@ -19,6 +19,7 @@ import BaseLayout from "../layouts/BaseLayout.astro";
|
||||
<div class="container">
|
||||
<div class="search-section">
|
||||
<div class="search-bar">
|
||||
<label for="search-input" class="sr-only">Search tools</label>
|
||||
<input
|
||||
type="text"
|
||||
id="search-input"
|
||||
@@ -27,17 +28,18 @@ import BaseLayout from "../layouts/BaseLayout.astro";
|
||||
/>
|
||||
</div>
|
||||
<div class="filters">
|
||||
<select id="filter-category" class="filter-select">
|
||||
<label for="filter-category" class="sr-only">Filter by category</label>
|
||||
<select id="filter-category" class="filter-select" aria-label="Filter by category">
|
||||
<option value="">All Categories</option>
|
||||
</select>
|
||||
<button id="clear-filters" class="btn btn-secondary btn-small"
|
||||
>Clear</button
|
||||
>
|
||||
</div>
|
||||
<div id="results-count" class="results-count"></div>
|
||||
<div id="results-count" class="results-count" aria-live="polite"></div>
|
||||
</div>
|
||||
|
||||
<div id="tools-list"></div>
|
||||
<div id="tools-list" role="list"></div>
|
||||
|
||||
<div class="coming-soon">
|
||||
<h2>More Tools Coming Soon</h2>
|
||||
|
||||
@@ -8,6 +8,7 @@ import { fetchFileContent, fetchData, getVSCodeInstallUrl, copyToClipboard, show
|
||||
let currentFilePath: string | null = null;
|
||||
let currentFileContent: string | null = null;
|
||||
let currentFileType: string | null = null;
|
||||
let triggerElement: HTMLElement | null = null;
|
||||
|
||||
// Collection data cache
|
||||
interface CollectionItem {
|
||||
@@ -31,6 +32,50 @@ interface CollectionsData {
|
||||
|
||||
let collectionsCache: CollectionsData | null = null;
|
||||
|
||||
/**
|
||||
* Get all focusable elements within a container
|
||||
*/
|
||||
function getFocusableElements(container: HTMLElement): HTMLElement[] {
|
||||
const focusableSelectors = [
|
||||
'button:not([disabled])',
|
||||
'a[href]',
|
||||
'input:not([disabled])',
|
||||
'select:not([disabled])',
|
||||
'textarea:not([disabled])',
|
||||
'[tabindex]:not([tabindex="-1"])'
|
||||
].join(', ');
|
||||
|
||||
return Array.from(container.querySelectorAll<HTMLElement>(focusableSelectors))
|
||||
.filter(el => el.offsetParent !== null); // Filter out hidden elements
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle keyboard navigation within modal (focus trap)
|
||||
*/
|
||||
function handleModalKeydown(e: KeyboardEvent, modal: HTMLElement): void {
|
||||
if (e.key === 'Tab') {
|
||||
const focusableElements = getFocusableElements(modal);
|
||||
if (focusableElements.length === 0) return;
|
||||
|
||||
const firstElement = focusableElements[0];
|
||||
const lastElement = focusableElements[focusableElements.length - 1];
|
||||
|
||||
if (e.shiftKey) {
|
||||
// Shift+Tab: if on first element, wrap to last
|
||||
if (document.activeElement === firstElement) {
|
||||
e.preventDefault();
|
||||
lastElement.focus();
|
||||
}
|
||||
} else {
|
||||
// Tab: if on last element, wrap to first
|
||||
if (document.activeElement === lastElement) {
|
||||
e.preventDefault();
|
||||
firstElement.focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup modal functionality
|
||||
*/
|
||||
@@ -50,8 +95,12 @@ export function setupModal(): void {
|
||||
});
|
||||
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape' && !modal.classList.contains('hidden')) {
|
||||
closeModal();
|
||||
if (!modal.classList.contains('hidden')) {
|
||||
if (e.key === 'Escape') {
|
||||
closeModal();
|
||||
} else {
|
||||
handleModalKeydown(e, modal);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -129,18 +178,72 @@ export function setupInstallDropdown(containerId: string): void {
|
||||
const container = document.getElementById(containerId);
|
||||
if (!container) return;
|
||||
|
||||
const toggle = container.querySelector('.install-btn-toggle');
|
||||
const toggle = container.querySelector<HTMLButtonElement>('.install-btn-toggle');
|
||||
const menu = container.querySelector('.install-dropdown-menu');
|
||||
const menuItems = container.querySelectorAll<HTMLAnchorElement>('.install-dropdown-menu a');
|
||||
|
||||
toggle?.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
container.classList.toggle('open');
|
||||
const isOpen = container.classList.toggle('open');
|
||||
toggle.setAttribute('aria-expanded', String(isOpen));
|
||||
|
||||
// Focus first menu item when opening
|
||||
if (isOpen && menuItems.length > 0) {
|
||||
menuItems[0].focus();
|
||||
}
|
||||
});
|
||||
|
||||
// Keyboard navigation for dropdown
|
||||
toggle?.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'ArrowDown' || e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
container.classList.add('open');
|
||||
toggle.setAttribute('aria-expanded', 'true');
|
||||
if (menuItems.length > 0) {
|
||||
menuItems[0].focus();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Keyboard navigation within menu
|
||||
menuItems.forEach((item, index) => {
|
||||
item.addEventListener('keydown', (e) => {
|
||||
switch (e.key) {
|
||||
case 'ArrowDown':
|
||||
e.preventDefault();
|
||||
if (index < menuItems.length - 1) {
|
||||
menuItems[index + 1].focus();
|
||||
}
|
||||
break;
|
||||
case 'ArrowUp':
|
||||
e.preventDefault();
|
||||
if (index > 0) {
|
||||
menuItems[index - 1].focus();
|
||||
} else {
|
||||
toggle?.focus();
|
||||
}
|
||||
break;
|
||||
case 'Escape':
|
||||
e.preventDefault();
|
||||
container.classList.remove('open');
|
||||
toggle?.setAttribute('aria-expanded', 'false');
|
||||
toggle?.focus();
|
||||
break;
|
||||
case 'Tab':
|
||||
// Close menu on tab out
|
||||
container.classList.remove('open');
|
||||
toggle?.setAttribute('aria-expanded', 'false');
|
||||
break;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Close dropdown when clicking outside
|
||||
document.addEventListener('click', (e) => {
|
||||
if (!container.contains(e.target as Node)) {
|
||||
container.classList.remove('open');
|
||||
toggle?.setAttribute('aria-expanded', 'false');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -148,6 +251,7 @@ export function setupInstallDropdown(containerId: string): void {
|
||||
container.querySelectorAll('.install-dropdown-menu a').forEach(link => {
|
||||
link.addEventListener('click', () => {
|
||||
container.classList.remove('open');
|
||||
toggle?.setAttribute('aria-expanded', 'false');
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -157,8 +261,9 @@ export function setupInstallDropdown(containerId: string): void {
|
||||
* @param filePath - Path to the file
|
||||
* @param type - Resource type (agent, prompt, instruction, etc.)
|
||||
* @param updateUrl - Whether to update the URL hash (default: true)
|
||||
* @param trigger - The element that triggered the modal (for focus return)
|
||||
*/
|
||||
export async function openFileModal(filePath: string, type: string, updateUrl = true): Promise<void> {
|
||||
export async function openFileModal(filePath: string, type: string, updateUrl = true, trigger?: HTMLElement): Promise<void> {
|
||||
const modal = document.getElementById('file-modal');
|
||||
const title = document.getElementById('modal-title');
|
||||
const modalContent = document.getElementById('modal-content');
|
||||
@@ -169,12 +274,16 @@ export async function openFileModal(filePath: string, type: string, updateUrl =
|
||||
const installInsiders = document.getElementById('install-insiders') as HTMLAnchorElement | null;
|
||||
const copyBtn = document.getElementById('copy-btn');
|
||||
const downloadBtn = document.getElementById('download-btn');
|
||||
const closeBtn = document.getElementById('close-modal');
|
||||
|
||||
if (!modal || !title || !modalContent) return;
|
||||
|
||||
currentFilePath = filePath;
|
||||
currentFileType = type;
|
||||
|
||||
// Track trigger element for focus return
|
||||
triggerElement = trigger || document.activeElement as HTMLElement;
|
||||
|
||||
// Update URL for deep linking
|
||||
if (updateUrl) {
|
||||
updateHash(filePath);
|
||||
@@ -183,6 +292,11 @@ export async function openFileModal(filePath: string, type: string, updateUrl =
|
||||
// Show modal with loading state
|
||||
title.textContent = filePath.split('/').pop() || filePath;
|
||||
modal.classList.remove('hidden');
|
||||
|
||||
// Set focus to close button for accessibility
|
||||
setTimeout(() => {
|
||||
closeBtn?.focus();
|
||||
}, 0);
|
||||
|
||||
// Handle collections differently - show as item list
|
||||
if (type === 'collection') {
|
||||
@@ -328,9 +442,15 @@ export function closeModal(updateUrl = true): void {
|
||||
updateHash(null);
|
||||
}
|
||||
|
||||
// Return focus to trigger element
|
||||
if (triggerElement && typeof triggerElement.focus === 'function') {
|
||||
triggerElement.focus();
|
||||
}
|
||||
|
||||
currentFilePath = null;
|
||||
currentFileContent = null;
|
||||
currentFileType = null;
|
||||
triggerElement = null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user