Skip to content

Commit 6c15123

Browse files
rdmuellerclaude
andcommitted
refactor: extract DocSidebar into separate component with CSS module
Move DocSidebar, Asciidoctor instance, and ADOC_SIDEBAR_STYLES to src/components/DocSidebar.jsx. Static layout styles moved to CSS module. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent bad7d8c commit 6c15123

3 files changed

Lines changed: 162 additions & 76 deletions

File tree

src/RiskRadar.jsx

Lines changed: 2 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,85 +1,11 @@
1-
import { useState, useRef, useEffect, useMemo } from "react";
2-
import Asciidoctor from "@asciidoctor/core";
1+
import { useState } from "react";
32
import T from "./i18n.js";
43
import { useTheme } from "./theme.js";
54
import { VERSION, TIER_BG, TYPE_COLORS } from "./constants.js";
65
import { getTierIndex, detectBrowserLanguage } from "./utils.js";
76
import RadarChart from "./components/RadarChart.jsx";
87
import MitigationCard from "./components/MitigationCard.jsx";
9-
10-
const adoc = Asciidoctor();
11-
12-
const ADOC_SIDEBAR_STYLES = `
13-
.adoc-content p { margin: 0.5em 0; }
14-
.adoc-content a { color: var(--link); text-decoration: underline; text-decoration-color: var(--link-underline); }
15-
.adoc-content a:hover { text-decoration-color: var(--link); }
16-
.adoc-content strong { color: var(--text-primary); }
17-
.adoc-content code { background: var(--bg-card); padding: 1px 4px; border-radius: 3px; font-size: 0.92em; }
18-
`;
19-
20-
function DocSidebar({ docs, open, onClose }) {
21-
const ref = useRef(null);
22-
useEffect(() => { if (open && ref.current) ref.current.scrollTop = 0; }, [open]);
23-
useEffect(() => {
24-
if (open && ref.current) {
25-
ref.current.querySelectorAll(".adoc-content a").forEach((a) => {
26-
a.setAttribute("target", "_blank");
27-
a.setAttribute("rel", "noopener");
28-
});
29-
}
30-
});
31-
32-
const rendered = useMemo(() =>
33-
docs.sections.map((sec) => ({
34-
...sec,
35-
html: adoc.convert(sec.content, { safe: "safe", attributes: { showtitle: false } }),
36-
})),
37-
[docs.sections]
38-
);
39-
40-
return (
41-
<div
42-
ref={ref}
43-
style={{
44-
position: "fixed", top: 0, right: 0, bottom: 0,
45-
width: open ? "min(480px, 85vw)" : "0",
46-
background: "var(--bg-sidebar)", borderLeft: open ? "1px solid var(--border)" : "none",
47-
overflowY: "auto", overflowX: "hidden",
48-
transition: "width 0.3s ease",
49-
zIndex: 1000,
50-
boxShadow: open ? "-8px 0 30px var(--shadow)" : "none",
51-
}}
52-
>
53-
{open && (
54-
<div style={{ padding: "24px 20px" }}>
55-
<style>{ADOC_SIDEBAR_STYLES}</style>
56-
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 24 }}>
57-
<h2 style={{ margin: 0, fontSize: 24, fontWeight: 700, color: "var(--text-heading)" }}>{docs.title}</h2>
58-
<button onClick={onClose} style={{ background: "var(--bg-card)", border: "1px solid var(--border)", borderRadius: 6, color: "var(--text-secondary)", padding: "6px 12px", cursor: "pointer", fontSize: 16 }}></button>
59-
</div>
60-
{rendered.map((sec) => (
61-
<div key={sec.id} style={{
62-
marginBottom: 28,
63-
...(sec.id === "disclaimer" ? { background: "var(--bg-card)", borderRadius: 10, padding: "14px 16px", border: "1px solid var(--border)" } : {}),
64-
}}>
65-
<h3 style={{ fontSize: 20, fontWeight: 700, color: sec.id === "disclaimer" ? "#f59e0b" : "var(--text-primary)", margin: "0 0 10px", paddingBottom: 6, borderBottom: sec.id === "disclaimer" ? "none" : "1px solid var(--border-subtle)" }}>
66-
{sec.id === "disclaimer" ? "\u26A0\uFE0F " : ""}{sec.title}
67-
</h3>
68-
<div
69-
className="adoc-content"
70-
style={{ fontSize: 18, color: "var(--text-secondary)", lineHeight: 1.7 }}
71-
dangerouslySetInnerHTML={{ __html: sec.html }}
72-
/>
73-
</div>
74-
))}
75-
<div style={{ borderTop: "1px solid var(--border-subtle)", paddingTop: 16, marginTop: 8, fontSize: 15, color: "var(--text-secondary)", textAlign: "center" }}>
76-
Generated with data from Veracode, CodeRabbit, BaxBench, Unit 42, Aikido Security, CSA, and others.
77-
</div>
78-
</div>
79-
)}
80-
</div>
81-
);
82-
}
8+
import DocSidebar from "./components/DocSidebar.jsx";
839

8410
export default function RiskRadar() {
8511
const [lang, setLang] = useState(() => {

src/components/DocSidebar.jsx

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { useRef, useEffect, useMemo } from "react";
2+
import Asciidoctor from "@asciidoctor/core";
3+
import styles from "./DocSidebar.module.css";
4+
5+
const adoc = Asciidoctor();
6+
7+
const ADOC_SIDEBAR_STYLES = `
8+
.adoc-content p { margin: 0.5em 0; }
9+
.adoc-content a { color: var(--link); text-decoration: underline; text-decoration-color: var(--link-underline); }
10+
.adoc-content a:hover { text-decoration-color: var(--link); }
11+
.adoc-content strong { color: var(--text-primary); }
12+
.adoc-content code { background: var(--bg-card); padding: 1px 4px; border-radius: 3px; font-size: 0.92em; }
13+
`;
14+
15+
export default function DocSidebar({ docs, open, onClose }) {
16+
const ref = useRef(null);
17+
useEffect(() => { if (open && ref.current) ref.current.scrollTop = 0; }, [open]);
18+
useEffect(() => {
19+
if (open && ref.current) {
20+
ref.current.querySelectorAll(".adoc-content a").forEach((a) => {
21+
a.setAttribute("target", "_blank");
22+
a.setAttribute("rel", "noopener");
23+
});
24+
}
25+
});
26+
27+
const rendered = useMemo(() =>
28+
docs.sections.map((sec) => ({
29+
...sec,
30+
html: adoc.convert(sec.content, { safe: "safe", attributes: { showtitle: false } }),
31+
})),
32+
[docs.sections]
33+
);
34+
35+
return (
36+
<div
37+
ref={ref}
38+
className={`${styles.sidebar} ${open ? styles.sidebarOpen : styles.sidebarClosed}`}
39+
>
40+
{open && (
41+
<div className={styles.content}>
42+
<style>{ADOC_SIDEBAR_STYLES}</style>
43+
<div className={styles.headerRow}>
44+
<h2 className={styles.title}>{docs.title}</h2>
45+
<button onClick={onClose} className={styles.closeBtn}></button>
46+
</div>
47+
{rendered.map((sec) => (
48+
<div key={sec.id} className={`${styles.section} ${sec.id === "disclaimer" ? styles.disclaimer : ""}`}>
49+
<h3 className={sec.id === "disclaimer" ? styles.disclaimerTitle : styles.sectionTitle}>
50+
{sec.id === "disclaimer" ? "\u26A0\uFE0F " : ""}{sec.title}
51+
</h3>
52+
<div
53+
className={`adoc-content ${styles.adocBody}`}
54+
dangerouslySetInnerHTML={{ __html: sec.html }}
55+
/>
56+
</div>
57+
))}
58+
<div className={styles.footer}>
59+
Generated with data from Veracode, CodeRabbit, BaxBench, Unit 42, Aikido Security, CSA, and others.
60+
</div>
61+
</div>
62+
)}
63+
</div>
64+
);
65+
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
.sidebar {
2+
position: fixed;
3+
top: 0;
4+
right: 0;
5+
bottom: 0;
6+
background: var(--bg-sidebar);
7+
overflow-y: auto;
8+
overflow-x: hidden;
9+
transition: width 0.3s ease;
10+
z-index: 1000;
11+
}
12+
13+
.sidebarOpen {
14+
width: min(480px, 85vw);
15+
border-left: 1px solid var(--border);
16+
box-shadow: -8px 0 30px var(--shadow);
17+
}
18+
19+
.sidebarClosed {
20+
width: 0;
21+
border-left: none;
22+
box-shadow: none;
23+
}
24+
25+
.content {
26+
padding: 24px 20px;
27+
}
28+
29+
.headerRow {
30+
display: flex;
31+
justify-content: space-between;
32+
align-items: center;
33+
margin-bottom: 24px;
34+
}
35+
36+
.title {
37+
margin: 0;
38+
font-size: 24px;
39+
font-weight: 700;
40+
color: var(--text-heading);
41+
}
42+
43+
.closeBtn {
44+
background: var(--bg-card);
45+
border: 1px solid var(--border);
46+
border-radius: 6px;
47+
color: var(--text-secondary);
48+
padding: 6px 12px;
49+
cursor: pointer;
50+
font-size: 16px;
51+
}
52+
53+
.section {
54+
margin-bottom: 28px;
55+
}
56+
57+
.disclaimer {
58+
background: var(--bg-card);
59+
border-radius: 10px;
60+
padding: 14px 16px;
61+
border: 1px solid var(--border);
62+
}
63+
64+
.sectionTitle {
65+
font-size: 20px;
66+
font-weight: 700;
67+
color: var(--text-primary);
68+
margin: 0 0 10px;
69+
padding-bottom: 6px;
70+
border-bottom: 1px solid var(--border-subtle);
71+
}
72+
73+
.disclaimerTitle {
74+
font-size: 20px;
75+
font-weight: 700;
76+
color: #f59e0b;
77+
margin: 0 0 10px;
78+
padding-bottom: 6px;
79+
border-bottom: none;
80+
}
81+
82+
.adocBody {
83+
font-size: 18px;
84+
color: var(--text-secondary);
85+
line-height: 1.7;
86+
}
87+
88+
.footer {
89+
border-top: 1px solid var(--border-subtle);
90+
padding-top: 16px;
91+
margin-top: 8px;
92+
font-size: 15px;
93+
color: var(--text-secondary);
94+
text-align: center;
95+
}

0 commit comments

Comments
 (0)