Skip to content

Commit c7fdc81

Browse files
committed
fix(docs): improve README rendering on item page
Signed-off-by: LakshanSS <lakshan230897@gmail.com>
1 parent 76c2ac6 commit c7fdc81

3 files changed

Lines changed: 90 additions & 24 deletions

File tree

src/components/PluginCard/PluginCard.module.css

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,12 @@
164164
text-decoration: none;
165165
}
166166

167+
.viewButton:focus-visible {
168+
outline: 2px solid var(--ifm-color-primary);
169+
outline-offset: 2px;
170+
box-shadow: 0 0 0 4px rgba(var(--ifm-color-primary-rgb, 0, 0, 0), 0.18);
171+
}
172+
167173
.comingSoon {
168174
display: inline-flex;
169175
align-items: center;

src/pages/ecosystem.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ export default function Ecosystem(): ReactNode {
146146
type="text"
147147
className={styles.searchInput}
148148
placeholder="Search the Ecosystem (e.g., AI, gateway, observability)"
149+
aria-label="Search the Ecosystem"
149150
value={searchQuery}
150151
onChange={(e) => onSearchChange(e.target.value)}
151152
/>

src/pages/ecosystem/item.tsx

Lines changed: 83 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -132,9 +132,22 @@ function CopyButton({ text }: { text: string }) {
132132
);
133133
}
134134

135-
function resolveImageSrc(src: string, rawBaseUrl: string): string {
136-
if (!src || /^(https?:)?\/\//i.test(src)) return src;
137-
return rawBaseUrl + src.replace(/^\.\//, '');
135+
function resolveUrl(src: string, rawBaseUrl: string): string {
136+
if (!src) return src;
137+
if (
138+
/^(https?:)?\/\//i.test(src) ||
139+
src.startsWith('mailto:') ||
140+
src.startsWith('#') ||
141+
src.startsWith('data:')
142+
) {
143+
return src;
144+
}
145+
if (!rawBaseUrl) return src;
146+
try {
147+
return new URL(src, rawBaseUrl).toString();
148+
} catch {
149+
return src;
150+
}
138151
}
139152

140153
function createMdComponents(rawBaseUrl: string) {
@@ -158,7 +171,7 @@ function createMdComponents(rawBaseUrl: string) {
158171
return <code className={styles.inlineCode}>{children}</code>;
159172
},
160173
img({ src, alt }: { src?: string; alt?: string }) {
161-
const resolvedSrc = src ? resolveImageSrc(src, rawBaseUrl) : undefined;
174+
const resolvedSrc = src ? resolveUrl(src, rawBaseUrl) : undefined;
162175
return (
163176
<img
164177
src={resolvedSrc}
@@ -168,6 +181,18 @@ function createMdComponents(rawBaseUrl: string) {
168181
/>
169182
);
170183
},
184+
a({ href, children }: { href?: string; children?: ReactNode }) {
185+
const resolvedHref = href ? resolveUrl(href, rawBaseUrl) : undefined;
186+
const isExternal = Boolean(resolvedHref && /^https?:\/\//i.test(resolvedHref));
187+
return (
188+
<a
189+
href={resolvedHref}
190+
{...(isExternal ? { target: '_blank', rel: 'noopener noreferrer' } : {})}
191+
>
192+
{children}
193+
</a>
194+
);
195+
},
171196
};
172197
}
173198

@@ -193,13 +218,24 @@ export default function EcosystemItem(): ReactNode {
193218
setReadmeError(false);
194219
setActiveTab(0);
195220
setLogoFailed(false);
196-
if (!rawUrl) return;
221+
if (!rawUrl) {
222+
setReadmeLoading(false);
223+
return;
224+
}
225+
const controller = new AbortController();
197226
setReadmeLoading(true);
198-
fetch(rawUrl)
227+
fetch(rawUrl, { signal: controller.signal })
199228
.then((r) => (r.ok ? r.text() : Promise.reject(new Error(`HTTP ${r.status}`))))
200-
.then(setReadmeRaw)
201-
.catch(() => setReadmeError(true))
202-
.finally(() => setReadmeLoading(false));
229+
.then((text) => {
230+
setReadmeRaw(text);
231+
setReadmeLoading(false);
232+
})
233+
.catch((err: unknown) => {
234+
if (err instanceof DOMException && err.name === 'AbortError') return;
235+
setReadmeError(true);
236+
setReadmeLoading(false);
237+
});
238+
return () => controller.abort();
203239
}, [rawUrl]);
204240

205241
const parsed = useMemo(
@@ -348,27 +384,50 @@ export default function EcosystemItem(): ReactNode {
348384
{/* Tab bar */}
349385
{hasTabs && (
350386
<div className={styles.tabBar}>
351-
<nav className={styles.tabList} aria-label="Section tabs">
352-
{parsed.sections.map((section, idx) => (
353-
<button
354-
key={section.id}
355-
className={
356-
idx === activeTab
357-
? `${styles.tab} ${styles.tabActive}`
358-
: styles.tab
359-
}
360-
onClick={() => setActiveTab(idx)}
361-
>
362-
{section.title}
363-
</button>
364-
))}
387+
<nav
388+
className={styles.tabList}
389+
role="tablist"
390+
aria-label="Section tabs"
391+
>
392+
{parsed.sections.map((section, idx) => {
393+
const isActive = idx === activeTab;
394+
return (
395+
<button
396+
key={section.id}
397+
id={`section-tab-${section.id}`}
398+
role="tab"
399+
type="button"
400+
aria-selected={isActive}
401+
aria-controls={`section-panel-${section.id}`}
402+
tabIndex={isActive ? 0 : -1}
403+
className={
404+
isActive
405+
? `${styles.tab} ${styles.tabActive}`
406+
: styles.tab
407+
}
408+
onClick={() => setActiveTab(idx)}
409+
>
410+
{section.title}
411+
</button>
412+
);
413+
})}
365414
</nav>
366415
</div>
367416
)}
368417

369418
{/* Content card */}
370419
{activeSection && (
371-
<div className={styles.contentCard}>
420+
<div
421+
className={styles.contentCard}
422+
{...(hasTabs
423+
? {
424+
role: 'tabpanel',
425+
id: `section-panel-${activeSection.id}`,
426+
'aria-labelledby': `section-tab-${activeSection.id}`,
427+
tabIndex: 0,
428+
}
429+
: {})}
430+
>
372431
{!hasTabs && (
373432
<h2 className={styles.singleSectionTitle}>
374433
{activeSection.title}

0 commit comments

Comments
 (0)