Skip to content

Commit 254fd88

Browse files
committed
feat: add Pagefind search functionality
- Add search modal with keyboard shortcut (⌘K / Ctrl+K) - Integrate Pagefind for static site search indexing - Style search results with scrollable list and hover scrollbar - Fix modal centering with scrollbar-gutter - Remove unnecessary padding from search results area
1 parent 5fcc9f4 commit 254fd88

3 files changed

Lines changed: 417 additions & 1 deletion

File tree

scripts/build.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,4 +56,17 @@ for await (const entry of Deno.readDir("./static")) {
5656
}
5757
}
5858

59+
// Generate search index with Pagefind
60+
console.log("\n🔍 Generating search index...");
61+
const pagefindCmd = new Deno.Command("pagefind", {
62+
args: ["--site", "dist"],
63+
stdout: "inherit",
64+
stderr: "inherit",
65+
});
66+
const pagefindResult = await pagefindCmd.output();
67+
if (!pagefindResult.success) {
68+
console.error("❌ Pagefind indexing failed");
69+
Deno.exit(1);
70+
}
71+
5972
console.log("\n🎉 Build complete!");

static/style.css

Lines changed: 320 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,8 +224,9 @@ body:has(.doc-layout) .global-header {
224224
gap: 0.75rem;
225225
}
226226

227-
/* Icon button base style (shared by theme-toggle and github-link) */
227+
/* Icon button base style (shared by theme-toggle, search-toggle, and github-link) */
228228
.theme-toggle,
229+
.search-toggle,
229230
.github-link {
230231
display: flex;
231232
align-items: center;
@@ -241,17 +242,34 @@ body:has(.doc-layout) .global-header {
241242
}
242243

243244
.theme-toggle:hover,
245+
.search-toggle:hover,
244246
.github-link:hover {
245247
color: var(--text-heading);
246248
border-color: var(--accent);
247249
background: rgba(99, 102, 241, 0.1);
248250
}
249251

250252
.theme-toggle i,
253+
.search-toggle i,
251254
.github-link i {
252255
font-size: 20px;
253256
}
254257

258+
/* Search toggle button */
259+
.search-toggle {
260+
width: auto;
261+
padding: 0 0.75rem;
262+
gap: 0.5rem;
263+
}
264+
265+
.search-shortcut {
266+
font-size: 0.7rem;
267+
padding: 0.15rem 0.4rem;
268+
background: var(--bg-code);
269+
border-radius: 4px;
270+
font-family: "JetBrains Mono", monospace;
271+
}
272+
255273
/* Theme toggle: show/hide sun/moon icons based on theme */
256274
.theme-toggle .icon-sun {
257275
display: none;
@@ -2446,12 +2464,23 @@ body:has(.api-layout) .global-header {
24462464
}
24472465

24482466
.theme-toggle,
2467+
.search-toggle,
24492468
.github-link {
24502469
width: 36px;
24512470
height: 36px;
24522471
}
24532472

2473+
.search-toggle {
2474+
width: 36px;
2475+
padding: 0;
2476+
}
2477+
2478+
.search-shortcut {
2479+
display: none;
2480+
}
2481+
24542482
.theme-toggle i,
2483+
.search-toggle i,
24552484
.github-link i {
24562485
font-size: 18px;
24572486
}
@@ -2645,3 +2674,293 @@ body:has(.api-layout) .global-header {
26452674
.overload-item .api-returns h5 {
26462675
font-size: 0.8rem;
26472676
}
2677+
2678+
/* ========================================
2679+
Search Modal
2680+
======================================== */
2681+
.search-modal {
2682+
display: none;
2683+
position: fixed;
2684+
inset: 0;
2685+
z-index: 1000;
2686+
background: rgba(0, 0, 0, 0.6);
2687+
backdrop-filter: blur(4px);
2688+
align-items: flex-start;
2689+
justify-content: center;
2690+
padding-top: 10vh;
2691+
/* Prevent layout shift from scrollbar */
2692+
overflow-y: auto;
2693+
scrollbar-gutter: stable both-edges;
2694+
}
2695+
2696+
.search-modal.open {
2697+
display: flex;
2698+
}
2699+
2700+
.search-modal-content {
2701+
background: var(--bg-card);
2702+
border: 1px solid var(--border);
2703+
border-radius: 12px;
2704+
width: 90%;
2705+
max-width: 720px;
2706+
max-height: 80vh;
2707+
overflow: hidden;
2708+
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);
2709+
}
2710+
2711+
.search-modal-header {
2712+
display: flex;
2713+
align-items: center;
2714+
justify-content: space-between;
2715+
padding: 1rem 1.25rem;
2716+
border-bottom: 1px solid var(--border);
2717+
}
2718+
2719+
.search-modal-title {
2720+
font-weight: 600;
2721+
color: var(--text-heading);
2722+
}
2723+
2724+
.search-modal-close {
2725+
display: flex;
2726+
align-items: center;
2727+
justify-content: center;
2728+
width: 32px;
2729+
height: 32px;
2730+
background: transparent;
2731+
border: none;
2732+
border-radius: 6px;
2733+
cursor: pointer;
2734+
color: var(--text-muted);
2735+
transition: all 0.2s;
2736+
}
2737+
2738+
.search-modal-close:hover {
2739+
background: var(--bg-code);
2740+
color: var(--text-heading);
2741+
}
2742+
2743+
.search-modal-close i {
2744+
font-size: 18px;
2745+
}
2746+
2747+
/* Pagefind UI customization */
2748+
#search-container {
2749+
--pagefind-ui-scale: 1;
2750+
--pagefind-ui-primary: var(--accent);
2751+
--pagefind-ui-text: var(--text-primary);
2752+
--pagefind-ui-background: var(--bg-card);
2753+
--pagefind-ui-border: var(--border);
2754+
--pagefind-ui-tag: var(--bg-code);
2755+
--pagefind-ui-border-width: 2px;
2756+
--pagefind-ui-border-radius: 8px;
2757+
--pagefind-ui-font: "Inter", sans-serif;
2758+
}
2759+
2760+
#search-container .pagefind-ui__form {
2761+
padding: 1rem 1.25rem;
2762+
}
2763+
2764+
#search-container .pagefind-ui__search-input {
2765+
background: var(--bg-primary);
2766+
color: var(--text-primary);
2767+
font-size: 1rem;
2768+
padding: 0.875rem 3rem 0.875rem 1rem;
2769+
border: 2px solid var(--border);
2770+
border-radius: 8px;
2771+
transition: border-color 0.2s;
2772+
width: 100%;
2773+
box-sizing: border-box;
2774+
}
2775+
2776+
#search-container .pagefind-ui__search-input:focus {
2777+
border-color: var(--accent);
2778+
outline: none;
2779+
}
2780+
2781+
/* Pagefind UI wrapper - ensure vertical layout */
2782+
#search-container .pagefind-ui {
2783+
display: block !important;
2784+
}
2785+
2786+
/* Search form wrapper */
2787+
#search-container .pagefind-ui__form {
2788+
position: relative;
2789+
margin-bottom: 0;
2790+
}
2791+
2792+
/* Hide default search icon */
2793+
#search-container .pagefind-ui__form::before {
2794+
display: none !important;
2795+
}
2796+
2797+
/* Clear button positioning - vertically centered in input */
2798+
#search-container .pagefind-ui__search-clear {
2799+
position: absolute;
2800+
color: var(--text-muted);
2801+
/* Form padding (1rem) + center of input (25px = 50px/2) - half button (16px = 32px/2) */
2802+
top: calc(1rem + 25px - 16px);
2803+
right: calc(1.25rem + 12px);
2804+
background: transparent;
2805+
border: none;
2806+
cursor: pointer;
2807+
padding: 0.5rem;
2808+
font-size: 0.875rem;
2809+
z-index: 10;
2810+
}
2811+
2812+
#search-container .pagefind-ui__search-clear:hover {
2813+
color: var(--text-primary);
2814+
}
2815+
2816+
/* Form needs position relative for absolute clear button */
2817+
#search-container .pagefind-ui__form {
2818+
position: relative;
2819+
}
2820+
2821+
#search-container .pagefind-ui__drawer {
2822+
padding: 0 0 1.25rem !important;
2823+
}
2824+
2825+
/* Results area - message stays fixed, results scroll */
2826+
#search-container .pagefind-ui__results-area {
2827+
display: flex;
2828+
flex-direction: column;
2829+
max-height: calc(70vh - 140px);
2830+
}
2831+
2832+
/* Results list - scrollable */
2833+
#search-container .pagefind-ui__results {
2834+
display: flex;
2835+
flex-direction: column;
2836+
gap: 0.5rem;
2837+
list-style: none;
2838+
padding-left: 0;
2839+
margin: 0;
2840+
flex: 1;
2841+
min-height: 0; /* Required for overflow to work in flex child */
2842+
overflow-y: auto;
2843+
/* Custom scrollbar - hidden by default, visible on hover */
2844+
scrollbar-width: thin;
2845+
scrollbar-color: transparent transparent;
2846+
}
2847+
2848+
#search-container .pagefind-ui__results:hover {
2849+
scrollbar-color: var(--border) transparent;
2850+
}
2851+
2852+
#search-container .pagefind-ui__results::-webkit-scrollbar {
2853+
width: 8px;
2854+
}
2855+
2856+
#search-container .pagefind-ui__results::-webkit-scrollbar-track {
2857+
background: transparent;
2858+
}
2859+
2860+
#search-container .pagefind-ui__results::-webkit-scrollbar-thumb {
2861+
background: transparent;
2862+
border-radius: 4px;
2863+
transition: background 0.2s;
2864+
}
2865+
2866+
#search-container .pagefind-ui__results:hover::-webkit-scrollbar-thumb {
2867+
background: var(--border);
2868+
}
2869+
2870+
#search-container .pagefind-ui__results::-webkit-scrollbar-thumb:hover {
2871+
background: var(--text-muted);
2872+
}
2873+
2874+
#search-container .pagefind-ui__message {
2875+
color: var(--text-muted);
2876+
font-size: 0.875rem;
2877+
padding: 0.5rem 0 1rem;
2878+
}
2879+
2880+
#search-container .pagefind-ui__result {
2881+
padding: 1rem;
2882+
background: var(--bg-primary);
2883+
border-radius: 8px;
2884+
border: 1px solid var(--border);
2885+
transition: border-color 0.2s;
2886+
flex-shrink: 0; /* Prevent cards from shrinking in flex container */
2887+
}
2888+
2889+
#search-container .pagefind-ui__result:hover {
2890+
border-color: var(--accent);
2891+
}
2892+
2893+
#search-container .pagefind-ui__result-title {
2894+
margin-bottom: 0.25rem;
2895+
}
2896+
2897+
#search-container .pagefind-ui__result-link {
2898+
color: var(--text-heading);
2899+
font-weight: 600;
2900+
font-size: 1rem;
2901+
}
2902+
2903+
#search-container .pagefind-ui__result-link:hover {
2904+
color: var(--accent);
2905+
}
2906+
2907+
#search-container .pagefind-ui__result-excerpt {
2908+
color: var(--text-muted);
2909+
font-size: 0.875rem;
2910+
line-height: 1.5;
2911+
}
2912+
2913+
/* Highlight matching text with accent color */
2914+
#search-container mark {
2915+
background: rgba(99, 102, 241, 0.25);
2916+
color: var(--accent-light);
2917+
padding: 0.1em 0.2em;
2918+
border-radius: 3px;
2919+
}
2920+
2921+
[data-theme="light"] #search-container mark {
2922+
background: rgba(99, 102, 241, 0.15);
2923+
color: var(--accent-dark);
2924+
}
2925+
2926+
/* Sub-results styling */
2927+
#search-container .pagefind-ui__result-nested {
2928+
margin-top: 0.75rem;
2929+
padding-top: 0.75rem;
2930+
border-top: 1px solid var(--border);
2931+
}
2932+
2933+
#search-container .pagefind-ui__result-nested .pagefind-ui__result-link {
2934+
font-size: 0.875rem;
2935+
font-weight: 500;
2936+
color: var(--link-color);
2937+
}
2938+
2939+
/* Load more button */
2940+
#search-container .pagefind-ui__button {
2941+
background: var(--accent);
2942+
color: white;
2943+
border: none;
2944+
padding: 0.75rem 1.5rem;
2945+
border-radius: 6px;
2946+
font-weight: 500;
2947+
cursor: pointer;
2948+
margin-top: 1rem;
2949+
transition: background 0.2s;
2950+
}
2951+
2952+
#search-container .pagefind-ui__button:hover {
2953+
background: var(--accent-dark);
2954+
}
2955+
2956+
/* Truncate long excerpts */
2957+
#search-container .pagefind-ui__result-excerpt {
2958+
display: -webkit-box;
2959+
-webkit-line-clamp: 2;
2960+
-webkit-box-orient: vertical;
2961+
overflow: hidden;
2962+
}
2963+
2964+
#search-container .pagefind-ui__result-nested .pagefind-ui__result-excerpt {
2965+
-webkit-line-clamp: 1;
2966+
}

0 commit comments

Comments
 (0)