Skip to content

Commit a761cbd

Browse files
committed
feat: add Export to spreadsheet button on packages page
- Export dropdown with language scope (current / all) and detail level (catalog only / catalog + CVE summary / full CVE rows) - Generates multi-tab .xlsx client-side via SheetJS (lazy-loaded chunk) - "Open in Sheets" button scaffolded but hidden until VITE_GOOGLE_CLIENT_ID is set; full setup guide added to ExportFeaturePlan.md
1 parent a60d176 commit a761cbd

9 files changed

Lines changed: 652 additions & 2 deletions

File tree

.env.example

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,9 @@ CATALOG_TOKEN=your_token_here
33
CATALOG_INDEX_ID=your-index-uuid-here
44
NPM_ORG_ID=your-org-id-here
55
NPM_BRIDGE_TOKEN=your_token_here
6+
7+
# Optional: enables "Open in Sheets" button in the Export dropdown.
8+
# Create an OAuth 2.0 client ID at console.cloud.google.com
9+
# (Application type: Web, add localhost:5173 + your gh-pages domain as authorized origins).
10+
# Enable the Google Drive API for the same project.
11+
VITE_GOOGLE_CLIENT_ID=

package-lock.json

Lines changed: 105 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
"dependencies": {
1212
"react": "^18.3.1",
1313
"react-dom": "^18.3.1",
14-
"react-router-dom": "^6.26.0"
14+
"react-router-dom": "^6.26.0",
15+
"xlsx": "^0.18.5"
1516
},
1617
"devDependencies": {
1718
"@types/react": "^18.3.1",
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
.wrap {
2+
position: relative;
3+
}
4+
5+
.trigger {
6+
display: inline-flex;
7+
align-items: center;
8+
gap: 6px;
9+
padding: 6px 14px;
10+
border-radius: 6px;
11+
border: 1px solid var(--border-strong);
12+
background: var(--bg-card);
13+
color: var(--text-secondary);
14+
font-family: 'Poppins', sans-serif;
15+
font-size: 12px;
16+
font-weight: 600;
17+
cursor: pointer;
18+
transition: all 0.15s;
19+
white-space: nowrap;
20+
}
21+
22+
.trigger:hover {
23+
border-color: var(--accent);
24+
color: var(--accent);
25+
background: var(--bg-accent);
26+
}
27+
28+
.panel {
29+
position: absolute;
30+
top: calc(100% + 6px);
31+
left: 0;
32+
z-index: 200;
33+
background: var(--bg-card);
34+
border: 1px solid var(--border-strong);
35+
border-radius: 10px;
36+
box-shadow: 0 8px 28px rgba(0, 0, 0, 0.15);
37+
padding: 14px;
38+
min-width: 230px;
39+
display: flex;
40+
flex-direction: column;
41+
gap: 10px;
42+
}
43+
44+
.group {
45+
display: flex;
46+
flex-direction: column;
47+
gap: 6px;
48+
}
49+
50+
.groupLabel {
51+
font-size: 10px;
52+
font-weight: 700;
53+
text-transform: uppercase;
54+
letter-spacing: 0.08em;
55+
color: var(--text-secondary);
56+
margin-bottom: 2px;
57+
}
58+
59+
.option {
60+
display: flex;
61+
align-items: center;
62+
gap: 8px;
63+
font-size: 12px;
64+
color: var(--text-primary);
65+
cursor: pointer;
66+
padding: 1px 0;
67+
user-select: none;
68+
}
69+
70+
.option input[type='radio'] {
71+
accent-color: var(--accent);
72+
cursor: pointer;
73+
}
74+
75+
.divider {
76+
border: none;
77+
border-top: 1px solid var(--border);
78+
margin: 2px 0;
79+
}
80+
81+
.actions {
82+
display: flex;
83+
flex-direction: column;
84+
gap: 7px;
85+
margin-top: 2px;
86+
}
87+
88+
.downloadBtn {
89+
width: 100%;
90+
padding: 8px;
91+
border-radius: 6px;
92+
border: none;
93+
background: var(--accent);
94+
color: #fff;
95+
font-family: 'Poppins', sans-serif;
96+
font-size: 12px;
97+
font-weight: 700;
98+
cursor: pointer;
99+
transition: opacity 0.15s;
100+
letter-spacing: 0.02em;
101+
}
102+
103+
.downloadBtn:hover:not(:disabled) {
104+
opacity: 0.88;
105+
}
106+
107+
.downloadBtn:disabled {
108+
opacity: 0.5;
109+
cursor: not-allowed;
110+
}
111+
112+
.sheetsBtn {
113+
width: 100%;
114+
padding: 7px 8px;
115+
border-radius: 6px;
116+
border: 1px solid var(--border-strong);
117+
background: var(--bg-card);
118+
color: var(--text-primary);
119+
font-family: 'Poppins', sans-serif;
120+
font-size: 12px;
121+
font-weight: 600;
122+
cursor: pointer;
123+
transition: border-color 0.15s, background 0.15s;
124+
display: flex;
125+
align-items: center;
126+
justify-content: center;
127+
gap: 7px;
128+
letter-spacing: 0.02em;
129+
}
130+
131+
.sheetsBtn:hover:not(:disabled) {
132+
border-color: #0f9d58;
133+
color: #0f9d58;
134+
background: rgba(15, 157, 88, 0.06);
135+
}
136+
137+
.sheetsBtn:disabled {
138+
opacity: 0.5;
139+
cursor: not-allowed;
140+
}
141+
142+
.sheetsBtnError {
143+
border-color: var(--status-vl-bd);
144+
color: var(--status-vl-tx);
145+
}

0 commit comments

Comments
 (0)