Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ jobs:
- name: Install dependencies
run: npm ci

- name: Install browser engine
run: npx playwright install --with-deps chromium

- name: Format
run: npm run fmt:check

Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ jobs:
- name: Install dependencies
run: npm ci

- name: Install browser engine
run: npx playwright install --with-deps chromium

- name: Validate source
run: npm run fmt:check && npm run lint && npm run typecheck

Expand Down
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,11 @@ Thumbs.db
# Screenshots
screenshots/
**/screenshots/
**/__screenshots__/
.codex-screenshots/
.vitest-attachments/

# Environment
.env
.env.*
!.env.example

77 changes: 75 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 6 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@askrjs/website",
"version": "0.1.1",
"version": "0.1.2",
"private": true,
"description": "Askr marketing and documentation site",
"license": "Apache-2.0",
Expand All @@ -17,9 +17,10 @@
"lint": "vp lint src tests scripts ssg.config.ts vite.config.ts",
"fmt": "vp fmt .",
"fmt:check": "vp fmt . --check",
"test:unit": "vp test run --exclude tests/generated-output-contract.test.ts",
"test:unit": "vp test run --exclude tests/generated-output-contract.test.ts --exclude \"tests/browser/**\"",
"test:static": "vp test run tests/generated-output-contract.test.ts",
"test": "npm run test:unit",
"test": "npm run test:unit && npm run test:browser",
"test:browser": "vp test run -c vitest.test.browser.config.ts --mode production",
"typecheck": "tsc --noEmit",
"check": "npm run fmt:check && npm run lint && npm run typecheck && npm test && npm run build",
"preview": "vp preview --outDir dist"
Expand Down Expand Up @@ -47,7 +48,9 @@
"@askrjs/vite": "0.0.13",
"@types/node": "^26.2.0",
"@typescript/native": "npm:typescript@^7.0.2",
"@vitest/browser-playwright": "4.1.10",
"monaco-editor": "^0.56.0",
"playwright": "^1.62.1",
"tsx": "4.23.11",
"typescript": "npm:@typescript/typescript6@^6.0.2",
"vite-plus": "^0.2.8"
Expand Down
100 changes: 88 additions & 12 deletions src/pages/docs/search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,24 @@ function loadSearchIndex(): Promise<typeof import('./search-index')> {
}

function DocsSearchInput(props: {
activeResultId: () => string | undefined;
close: () => void;
onKeyDown: (event: KeyboardEvent) => void;
runSearch: (value: string | undefined) => Promise<void>;
}) {
return (
<CommandHeader class="docs-search__input">
<SearchIcon size={18} aria-hidden="true" />
<CommandInput
data-docs-search-input
role="combobox"
aria-autocomplete="list"
aria-controls="docs-search-results"
aria-expanded="true"
aria-activedescendant={props.activeResultId()}
placeholder="Search concepts, imports, and API symbols"
aria-label="Search documentation"
onKeyDown={props.onKeyDown}
onInput={(event: Event) =>
void props.runSearch((event.currentTarget as HTMLInputElement).value)
}
Expand All @@ -52,11 +60,44 @@ function DocsSearchInput(props: {
);
}

function searchResultId(index: number): string {
return `docs-search-result-${index}`;
}

function DocsSearchResult(props: {
activeIndex: () => number;
index: () => number;
result: DocsSearchRecord;
setActiveIndex: (index: number) => void;
}) {
const index = props.index();
const active = props.activeIndex() === index;
return (
<CommandPaletteLink
id={searchResultId(index)}
href={`${props.result.route}${props.result.anchor ? `#${props.result.anchor}` : ''}`}
role="option"
aria-selected={active ? 'true' : 'false'}
data-active={active ? 'true' : undefined}
data-docs-search-result
onPointerEnter={() => props.setActiveIndex(props.index())}
>
<span>
<strong>{props.result.title}</strong>
<small>{props.result.description}</small>
</span>
<em>{props.result.group}</em>
</CommandPaletteLink>
);
}

function DocsSearchResults(props: {
activeIndex: () => number;
error: () => boolean;
loading: () => boolean;
query: () => string;
results: () => DocsSearchRecord[];
setActiveIndex: (index: number) => void;
}) {
return (
<div
Expand Down Expand Up @@ -85,21 +126,18 @@ function DocsSearchResults(props: {
</Show>
}
>
<CommandPaletteList>
<CommandPaletteList id="docs-search-results" role="listbox">
<For
each={props.results}
by={(result) => `${result.route}#${result.anchor ?? ''}`}
>
{(result) => (
<CommandPaletteLink
href={`${result.route}${result.anchor ? `#${result.anchor}` : ''}`}
>
<span>
<strong>{result.title}</strong>
<small>{result.description}</small>
</span>
<em>{result.group}</em>
</CommandPaletteLink>
{(result, index) => (
<DocsSearchResult
activeIndex={props.activeIndex}
index={index}
result={result}
setActiveIndex={props.setActiveIndex}
/>
)}
</For>
</CommandPaletteList>
Expand All @@ -116,13 +154,15 @@ function DocsSearchResults(props: {
}

function DocsSearchContent(props: DocsSearchContentProps) {
const [activeIndex, setActiveIndex] = state(-1);
const [query, setQuery] = state('');
const [loading, setLoading] = state(false);
const [results, setResults] = state<DocsSearchRecord[]>([]);
const [error, setError] = state(false);

const runSearch = async (value: string | undefined): Promise<void> => {
const nextValue = value ?? '';
setActiveIndex(-1);
setQuery(nextValue);
setError(false);
if (!nextValue.trim()) {
Expand All @@ -143,14 +183,50 @@ function DocsSearchContent(props: DocsSearchContentProps) {
if ((query() ?? '') === nextValue && props.isOpen()) setLoading(false);
}
};
const activeResultId = (): string | undefined => {
const index = activeIndex();
return index >= 0 && index < results().length
? searchResultId(index)
: undefined;
};
const onSearchKeyDown = (event: KeyboardEvent): void => {
const count = results().length;
if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
if (count === 0) return;
event.preventDefault();
const current = activeIndex();
setActiveIndex(
event.key === 'ArrowDown'
? current < 0 || current >= count - 1
? 0
: current + 1
: current <= 0
? count - 1
: current - 1
);
return;
}
if (event.key !== 'Enter') return;
const id = activeResultId();
if (!id) return;
event.preventDefault();
document.getElementById(id)?.click();
};
return (
<>
<DocsSearchInput close={props.close} runSearch={runSearch} />
<DocsSearchInput
activeResultId={activeResultId}
close={props.close}
onKeyDown={onSearchKeyDown}
runSearch={runSearch}
/>
<DocsSearchResults
activeIndex={activeIndex}
error={error}
loading={loading}
query={query}
results={results}
setActiveIndex={setActiveIndex}
/>
</>
);
Expand Down
3 changes: 2 additions & 1 deletion src/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -1383,7 +1383,8 @@ html[data-theme='dark'] .github-mark img.github-mark__dark {
text-decoration: none;
}

.docs-search__results a:hover {
.docs-search__results a:hover,
.docs-search__results a[data-active='true'] {
background: var(--ak-color-primary-soft);
}

Expand Down
Loading