Skip to content

Commit fa9911a

Browse files
committed
feat: UI refresh with scroll-to-top, mobile topbar, sidebar brand/footer, type dots, and brand color system (v0.8.0)
1 parent 019febc commit fa9911a

File tree

7 files changed

+1453
-758
lines changed

7 files changed

+1453
-758
lines changed

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ dist/
33
*.log
44
.DS_Store
55
.idea
6-
src/assets-inline.ts
76
preview.html
87

98
bun.lock

CHANGELOG.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,31 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project
66
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [0.8.0] - 2026-04-04
9+
10+
### Added
11+
12+
- **Scroll-to-Top Button** - A floating button appears on the page to quickly scroll back to the top
13+
- **Mobile Top Bar** - Redesigned mobile navigation with a topbar showing the docs title and a
14+
config button alongside the menu toggle
15+
- **Sidebar Brand & Footer** - Sidebar now displays the docs title as a brand label in the header
16+
and a GitHub link in a new footer section
17+
- **Type Indicator Dots** - Sidebar links now show a colored dot indicating the procedure type
18+
(query/mutation) at a glance
19+
- **Header Pill Badge** - The main header subtitle uses a styled "tRPC" pill badge for a cleaner
20+
look
21+
- **`__internal` Exports** - Internal helper functions are now exported under `__internal` from
22+
`collect-routes.ts` to support white-box unit testing
23+
24+
### Changed
25+
26+
- **UI Design Refresh** - Comprehensive CSS update introducing a unified `--brand-*` color token
27+
system, refined button styles, improved spacing, and updated font stacks across all components
28+
- **Search Filter Bar** - Moved outside the `<header>` element for better layout flow; results count
29+
and clear-filters button reordered within the controls area
30+
31+
---
32+
833
## [0.7.0] - 2026-03-15
934

1035
### Added

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "trpc-docs-generator",
3-
"version": "0.7.0",
3+
"version": "0.8.0",
44
"author": "Lior Cohen",
55
"homepage": "https://github.com/liorcodev/trpc-docs-generator#readme",
66
"repository": {
@@ -46,6 +46,7 @@
4646
],
4747
"scripts": {
4848
"format": "prettier --write .",
49+
"generate": "bun scripts/generate-preview.ts",
4950
"prebuild": "bun scripts/build-inline.js",
5051
"build": "tsc",
5152
"prepublishOnly": "bun run build"

src/assets-inline.ts

Lines changed: 19 additions & 0 deletions
Large diffs are not rendered by default.

src/generate-html.ts

Lines changed: 116 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ export function generateDocsHtml(routes: RouteInfo[], options: DocsGeneratorOpti
2828
<span id="configButtonText">Configure Base URL</span>
2929
</button>
3030
31+
<button class="scroll-top-btn" id="scrollTopBtn" onclick="window.scrollTo({top:0,behavior:'smooth'})" aria-label="Scroll to top">
32+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M18 15l-6-6-6 6"/></svg>
33+
</button>
34+
3135
<div class="config-modal" id="configModal">
3236
<div class="config-modal-content">
3337
<div class="config-modal-header">
@@ -60,44 +64,65 @@ export function generateDocsHtml(routes: RouteInfo[], options: DocsGeneratorOpti
6064
</div>
6165
</div>
6266
63-
<button class="mobile-menu-toggle" id="mobileMenuToggle" aria-label="Toggle navigation">
64-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
65-
<line x1="3" y1="12" x2="21" y2="12"></line>
66-
<line x1="3" y1="6" x2="21" y2="6"></line>
67-
<line x1="3" y1="18" x2="21" y2="18"></line>
68-
</svg>
69-
</button>
67+
<div class="mobile-topbar">
68+
<button class="mobile-topbar-toggle" id="mobileMenuToggle" aria-label="Toggle navigation">
69+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
70+
<line x1="3" y1="12" x2="21" y2="12"></line>
71+
<line x1="3" y1="6" x2="21" y2="6"></line>
72+
<line x1="3" y1="18" x2="21" y2="18"></line>
73+
</svg>
74+
</button>
75+
<span class="mobile-topbar-title">${title}</span>
76+
<button class="mobile-topbar-config" id="mobileConfigButton" onclick="openConfigModal()" aria-label="Configure Base URL">
77+
<span class="iconify" data-icon="mdi:cog" style="width: 18px; height: 18px;"></span>
78+
</button>
79+
</div>
7080
<div class="sidebar-overlay" id="sidebarOverlay"></div>
7181
<div class="container">
7282
<nav class="sidebar" id="sidebar">
7383
<div class="sidebar-header">
7484
<img src="${getLogo()}" alt="Logo" class="sidebar-logo" />
85+
<span class="sidebar-brand">${title}</span>
86+
</div>
87+
<div class="sidebar-nav">
88+
<div class="sidebar-title">Navigation</div>
89+
${Object.entries(groupedRoutes)
90+
.map(
91+
([group, groupRoutes]) => `
92+
<div class="sidebar-group">
93+
<div class="sidebar-group-title">${formatGroupName(group)}</div>
94+
${groupRoutes
95+
.map(
96+
route => `
97+
<a href="#${route.path.replace(/\./g, '-')}" class="sidebar-link">
98+
<span class="link-type-dot dot-${route.type}"></span>
99+
${route.meta?.name ?? route.path.split('.').pop()}
100+
</a>
101+
`
102+
)
103+
.join('')}
104+
</div>
105+
`
106+
)
107+
.join('')}
108+
</div>
109+
<div class="sidebar-footer">
110+
<a href="https://github.com/liorcodev/trpc-docs-generator" target="_blank" rel="noopener noreferrer" class="sidebar-footer-link" title="View on GitHub">
111+
<span class="iconify" data-icon="mdi:github" style="width: 20px; height: 20px;"></span>
112+
<span>View on GitHub</span>
113+
</a>
75114
</div>
76-
<div class="sidebar-title"><span class="iconify" data-icon="mdi:book-open-page-variant" style="vertical-align: -0.125em; margin-right: 0.5rem;"></span>Navigation</div>
77-
${Object.entries(groupedRoutes)
78-
.map(
79-
([group, groupRoutes]) => `
80-
<div class="sidebar-group">
81-
<div class="sidebar-group-title">${formatGroupName(group)}</div>
82-
${groupRoutes
83-
.map(
84-
route => `
85-
<a href="#${route.path.replace(/\./g, '-')}" class="sidebar-link">
86-
${route.meta?.name ?? route.path.split('.').pop()}
87-
</a>
88-
`
89-
)
90-
.join('')}
91-
</div>
92-
`
93-
)
94-
.join('')}
95115
</nav>
96116
97117
<main class="main-content">
98118
<header>
99-
<h1>${title}</h1>
100-
<p><span class="iconify" data-icon="mdi:sparkles" style="vertical-align: -0.125em;"></span> tRPC API Documentation <span class="iconify" data-icon="mdi:sparkles" style="vertical-align: -0.125em;"></span></p>
119+
<div class="header-top">
120+
<h1>${title}</h1>
121+
<p class="header-subtitle">
122+
<span class="header-pill">tRPC</span>
123+
API Documentation
124+
</p>
125+
</div>
101126
<div class="stats">
102127
<div class="stat">
103128
<div class="stat-value">${routes.length}</div>
@@ -122,65 +147,66 @@ export function generateDocsHtml(routes: RouteInfo[], options: DocsGeneratorOpti
122147
</div>
123148
</div>
124149
</div>
150+
</header>
125151
126-
<div class="search-filter-bar">
127-
<div class="search-box">
128-
<span class="iconify search-icon" data-icon="mdi:magnify"></span>
129-
<input
130-
type="search"
131-
id="searchInput"
132-
class="search-input"
133-
placeholder="Search endpoints, descriptions..."
134-
/>
135-
</div>
136-
137-
<div class="filter-group">
138-
<select id="typeFilter" class="filter-select">
139-
<option value="all">All Types</option>
140-
<option value="query">Queries</option>
141-
<option value="mutation">Mutations</option>
142-
</select>
143-
144-
<select id="authFilter" class="filter-select">
145-
<option value="all">All Endpoints</option>
146-
<option value="public">Public</option>
147-
<option value="protected">Protected</option>
148-
</select>
149-
150-
<select id="tagFilter" class="filter-select">
151-
<option value="all">All Tags</option>
152-
${Array.from(
153-
new Set(routes.flatMap(r => (r.meta?.docs as RouteMeta['docs'])?.tags || []))
154-
)
155-
.sort()
156-
.map(tag => `<option value="${escapeHtml(tag)}">${escapeHtml(tag)}</option>`)
157-
.join('')}
158-
</select>
159-
</div>
152+
<div class="search-filter-bar">
153+
<div class="search-box">
154+
<span class="iconify search-icon" data-icon="mdi:magnify"></span>
155+
<input
156+
type="search"
157+
id="searchInput"
158+
class="search-input"
159+
placeholder="Search endpoints, descriptions..."
160+
/>
161+
</div>
162+
163+
<div class="filter-group">
164+
<select id="typeFilter" class="filter-select">
165+
<option value="all">All Types</option>
166+
<option value="query">Queries</option>
167+
<option value="mutation">Mutations</option>
168+
</select>
160169
161-
<div class="filter-controls">
162-
<button class="clear-filters-btn" id="clearFiltersBtn" onclick="clearFilters()">
163-
<span class="iconify" data-icon="mdi:filter-remove" style="width: 14px; height: 14px;"></span>
164-
Clear Filters
165-
</button>
166-
</div>
170+
<select id="authFilter" class="filter-select">
171+
<option value="all">All Endpoints</option>
172+
<option value="public">Public</option>
173+
<option value="protected">Protected</option>
174+
</select>
167175
176+
<select id="tagFilter" class="filter-select">
177+
<option value="all">All Tags</option>
178+
${Array.from(
179+
new Set(routes.flatMap(r => (r.meta?.docs as RouteMeta['docs'])?.tags || []))
180+
)
181+
.sort()
182+
.map(tag => `<option value="${escapeHtml(tag)}">${escapeHtml(tag)}</option>`)
183+
.join('')}
184+
</select>
185+
</div>
186+
187+
<div class="filter-controls">
168188
<div class="results-count" id="resultsCount">
169189
Showing ${routes.length} of ${routes.length} endpoints
170190
</div>
191+
<button class="clear-filters-btn" id="clearFiltersBtn" onclick="clearFilters()">
192+
<span class="iconify" data-icon="mdi:filter-remove" style="width: 14px; height: 14px;"></span>
193+
Clear Filters
194+
</button>
171195
</div>
172-
</header>
196+
</div>
173197
174-
${Object.entries(groupedRoutes)
175-
.map(
176-
([group, groupRoutes]) => `
177-
<section class="route-group" id="group-${group}">
178-
<h2 class="route-group-header">${formatGroupName(group)}</h2>
179-
${groupRoutes.map(route => generateRouteCard(route)).join('')}
180-
</section>
181-
`
182-
)
183-
.join('')}
198+
<div class="main-body">
199+
${Object.entries(groupedRoutes)
200+
.map(
201+
([group, groupRoutes]) => `
202+
<section class="route-group" id="group-${group}">
203+
<h2 class="route-group-header">${formatGroupName(group)}</h2>
204+
${groupRoutes.map(route => generateRouteCard(route)).join('')}
205+
</section>
206+
`
207+
)
208+
.join('')}
209+
</div>
184210
</main>
185211
</div>
186212
@@ -240,7 +266,7 @@ function generateRouteCard(route: RouteInfo): string {
240266
.toLowerCase();
241267

242268
return `
243-
<div class="route-card"
269+
<div class="route-card type-${route.type}"
244270
id="${routeId}"
245271
data-type="${route.type}"
246272
data-auth="${docs?.auth ? 'true' : 'false'}"
@@ -293,8 +319,10 @@ function generateRouteCard(route: RouteInfo): string {
293319
? `
294320
<div class="route-section">
295321
<div class="route-section-title"><span class="iconify" data-icon="mdi:import" style="vertical-align: -0.125em; margin-right: 0.5rem;"></span>Input Schema</div>
296-
<div class="schema-block">
297-
<pre>${escapeHtml(route.inputTypeScript)}</pre>
322+
<div class="schema-block"> <div class="schema-block-header">
323+
<span class="schema-lang-label">TypeScript</span>
324+
<button class="copy-btn" onclick="copySchema(this)">Copy</button>
325+
</div> <pre>${escapeHtml(route.inputTypeScript)}</pre>
298326
</div>
299327
</div>
300328
`
@@ -306,8 +334,10 @@ function generateRouteCard(route: RouteInfo): string {
306334
? `
307335
<div class="route-section">
308336
<div class="route-section-title"><span class="iconify" data-icon="mdi:export" style="vertical-align: -0.125em; margin-right: 0.5rem;"></span>Output Schema</div>
309-
<div class="schema-block">
310-
<pre>${escapeHtml(route.outputTypeScript)}</pre>
337+
<div class="schema-block"> <div class="schema-block-header">
338+
<span class="schema-lang-label">TypeScript</span>
339+
<button class="copy-btn" onclick="copySchema(this)">Copy</button>
340+
</div> <pre>${escapeHtml(route.outputTypeScript)}</pre>
311341
</div>
312342
</div>
313343
`
@@ -321,6 +351,7 @@ function generateRouteCard(route: RouteInfo): string {
321351
Test Endpoint
322352
</div>
323353
</div>
354+
<div class="test-panel-body">
324355
325356
<div class="headers-manager">
326357
<label class="test-section-label">Request Headers</label>
@@ -392,6 +423,7 @@ function generateRouteCard(route: RouteInfo): string {
392423
</button>
393424
394425
<div id="response-${routeId}" class="response-container" style="display: none;"></div>
426+
</div>
395427
</div>
396428
</div>
397429
</div>

0 commit comments

Comments
 (0)